<commit_before>package nodb

import (
	"encoding/binary"
	"errors"
	"time"

	"github.com/lunny/nodb/store"
)

const (
	listHeadSeq int32 = 1
	listTailSeq int32 = 2

	listMinSeq     int32 = 1000
	listMaxSeq     int32 = 1<<31 - 1000
	listInitialSeq int32 = listMinSeq + (listMaxSeq-listMinSeq)/2
)

var errLMetaKey = errors.New("invalid lmeta key")
var errListKey = errors.New("invalid list key")
var errListSeq = errors.New("invalid list sequence, overflow")

func (db *DB) lEncodeMetaKey(key []byte) []byte {
	buf := make([]byte, len(key)+2)
	buf[0] = db.index
	buf[1] = LMetaType

	copy(buf[2:], key)
	return buf
}

func (db *DB) lDecodeMetaKey(ek []byte) ([]byte, error) {
	if len(ek) < 2 || ek[0] != db.index || ek[1] != LMetaType {
		return nil, errLMetaKey
	}

	return ek[2:], nil
}

func (db *DB) lEncodeListKey(key []byte, seq int32) []byte {
	buf := make([]byte, len(key)+8)

	pos := 0
	buf[pos] = db.index
	pos++
	buf[pos] = ListType
	pos++

	binary.BigEndian.PutUint16(buf[pos:], uint16(len(key)))
	pos += 2

	copy(buf[pos:], key)
	pos += len(key)

	binary.BigEndian.PutUint32(buf[pos:], uint32(seq))

	return buf
}

func (db *DB) lDecodeListKey(ek []byte) (key []byte, seq int32, err error) {
	if len(ek) < 8 || ek[0] != db.index || ek[1] != ListType {
		err = errListKey
		return
	}

	keyLen := int(binary.BigEndian.Uint16(ek[2:]))
	if keyLen+8 != len(ek) {
		err = errListKey
		return
	}

	key = ek[4 : 4+keyLen]
	seq = int32(binary.BigEndian.Uint32(ek[4+keyLen:]))
	return
}

func (db *DB) lpush(key []byte, whereSeq int32, args ...[]byte) (int64, error) {
	if err := checkKeySize(key); err != nil {
		return 0, err
	}

	var headSeq int32
	var tailSeq int32
	var size int32
	var err error

	t := db.listBatch
	t.Lock()
	defer t.Unlock()

	metaKey := db.lEncodeMetaKey(key)
	headSeq, tailSeq, size, err = db.lGetMeta(nil, metaKey)
	if err != nil {
		return 0, err
	}

	var pushCnt int = len(args)
	if pushCnt == 0 {
		return int64(size), nil
	}

	var seq int32 = headSeq
	var delta int32 = -1
	if whereSeq == listTailSeq {
		seq = tailSeq
		delta = 1
	}

	//	append elements
	if size > 0 {
		seq += delta
	}

	for i := 0; i < pushCnt; i++ {
		ek := db.lEncodeListKey(key, seq+int32(i)*delta)
		t.Put(ek, args[i])
	}

	seq += int32(pushCnt-1) * delta
	if seq <= listMinSeq || seq >= listMaxSeq {
		return 0, errListSeq
	}

	//	set meta info
	if whereSeq == listHeadSeq {
		headSeq = seq
	} else {
		tailSeq = seq
	}

	db.lSetMeta(metaKey, headSeq, tailSeq)

	err = t.Commit()
	return int64(size) + int64(pushCnt), err
}

func (db *DB) lpop(key []byte, whereSeq int32) ([]byte, error) {
	if err := checkKeySize(key); err != nil {
		return nil, err
	}

	t := db.listBatch
	t.Lock()
	defer t.Unlock()

	var headSeq int32
	var tailSeq int32
	var err error

	metaKey := db.lEncodeMetaKey(key)
	headSeq, tailSeq, _, err = db.lGetMeta(nil, metaKey)
	if err != nil {
		return nil, err
	}

	var value []byte

	var seq int32 = headSeq
	if whereSeq == listTailSeq {
		seq = tailSeq
	}

	itemKey := db.lEncodeListKey(key, seq)
	value, err = db.bucket.Get(itemKey)
	if err != nil {
		return nil, err
	}

	if whereSeq == listHeadSeq {
		headSeq += 1
	} else {
		tailSeq -= 1
	}

	t.Delete(itemKey)
	size := db.lSetMeta(metaKey, headSeq, tailSeq)
	if size == 0 {
		db.rmExpire(t, HashType, key)
	}

	err = t.Commit()
	return value, err
}

//	ps : here just focus on deleting the list data,
//		 any other likes expire is ignore.
func (db *DB) lDelete(t *batch, key []byte) int64 {
	mk := db.lEncodeMetaKey(key)

	var headSeq int32
	var tailSeq int32
	var err error

	it := db.bucket.NewIterator()
	defer it.Close()

	headSeq, tailSeq, _, err = db.lGetMeta(it, mk)
	if err != nil {
		return 0
	}

	var num int64 = 0
	startKey := db.lEncodeListKey(key, headSeq)
	stopKey := db.lEncodeListKey(key, tailSeq)

	rit := store.NewRangeIterator(it, &store.Range{startKey, stopKey, store.RangeClose})
	for ; rit.Valid(); rit.Next() {
		t.Delete(rit.RawKey())
		num++
	}

	t.Delete(mk)

	return num
}

func (db *DB) lGetMeta(it *store.Iterator, ek []byte) (headSeq int32, tailSeq int32, size int32, err error) {
	var v []byte
	if it != nil {
		v = it.Find(ek)
	} else {
		v, err = db.bucket.Get(ek)
	}
	if err != nil {
		return
	} else if v == nil {
		headSeq = listInitialSeq
		tailSeq = listInitialSeq
		size = 0
		return
	} else {
		headSeq = int32(binary.LittleEndian.Uint32(v[0:4]))
		tailSeq = int32(binary.LittleEndian.Uint32(v[4:8]))
		size = tailSeq - headSeq + 1
	}
	return
}

func (db *DB) lSetMeta(ek []byte, headSeq int32, tailSeq int32) int32 {
	t := db.listBatch

	var size int32 = tailSeq - headSeq + 1
	if size < 0 {
		//	todo : log error + panic
	} else if size == 0 {
		t.Delete(ek)
	} else {
		buf := make([]byte, 8)

		binary.LittleEndian.PutUint32(buf[0:4], uint32(headSeq))
		binary.LittleEndian.PutUint32(buf[4:8], uint32(tailSeq))

		t.Put(ek, buf)
	}

	return size
}

func (db *DB) lExpireAt(key []byte, when int64) (int64, error) {
	t := db.listBatch
	t.Lock()
	defer t.Unlock()

	if llen, err := db.LLen(key); err != nil || llen == 0 {
		return 0, err
	} else {
		db.expireAt(t, ListType, key, when)
		if err := t.Commit(); err != nil {
			return 0, err
		}
	}
	return 1, nil
}

func (db *DB) LIndex(key []byte, index int32) ([]byte, error) {
	if err := checkKeySize(key); err != nil {
		return nil, err
	}

	var seq int32
	var headSeq int32
	var tailSeq int32
	var err error

	metaKey := db.lEncodeMetaKey(key)

	it := db.bucket.NewIterator()
	defer it.Close()

	headSeq, tailSeq, _, err = db.lGetMeta(it, metaKey)
	if err != nil {
		return nil, err
	}

	if index >= 0 {
		seq = headSeq + index
	} else {
		seq = tailSeq + index + 1
	}

	sk := db.lEncodeListKey(key, seq)
	v := it.Find(sk)

	return v, nil
}

func (db *DB) LLen(key []byte) (int64, error) {
	if err := checkKeySize(key); err != nil {
		return 0, err
	}

	ek := db.lEncodeMetaKey(key)
	_, _, size, err := db.lGetMeta(nil, ek)
	return int64(size), err
}

func (db *DB) LPop(key []byte) ([]byte, error) {
	return db.lpop(key, listHeadSeq)
}

func (db *DB) LPush(key []byte, arg1 []byte, args ...[]byte) (int64, error) {
	return db.lpush(key, listHeadSeq, arg1, args...)
}

func (db *DB) LRange(key []byte, start int32, stop int32) ([][]byte, error) {
	if err := checkKeySize(key); err != nil {
		return nil, err
	}

	var headSeq int32
	var llen int32
	var err error

	metaKey := db.lEncodeMetaKey(key)

	it := db.bucket.NewIterator()
	defer it.Close()

	if headSeq, _, llen, err = db.lGetMeta(it, metaKey); err != nil {
		return nil, err
	}

	if start < 0 {
		start = llen + start
	}
	if stop < 0 {
		stop = llen + stop
	}
	if start < 0 {
		start = 0
	}

	if start > stop || start >= llen {
		return [][]byte{}, nil
	}

	if stop >= llen {
		stop = llen - 1
	}

	limit := (stop - start) + 1
	headSeq += start

	v := make([][]byte, 0, limit)

	startKey := db.lEncodeListKey(key, headSeq)
	rit := store.NewRangeLimitIterator(it,
		&store.Range{
			Min:  startKey,
			Max:  nil,
			Type: store.RangeClose},
		&store.Limit{
			Offset: 0,
			Count:  int(limit)})

	for ; rit.Valid(); rit.Next() {
		v = append(v, rit.Value())
	}

	return v, nil
}

func (db *DB) RPop(key []byte) ([]byte, error) {
	return db.lpop(key, listTailSeq)
}

func (db *DB) RPush(key []byte, arg1 []byte, args ...[]byte) (int64, error) {
	return db.lpush(key, listTailSeq, arg1, args...)
}

func (db *DB) LClear(key []byte) (int64, error) {
	if err := checkKeySize(key); err != nil {
		return 0, err
	}

	t := db.listBatch
	t.Lock()
	defer t.Unlock()

	num := db.lDelete(t, key)
	db.rmExpire(t, ListType, key)

	err := t.Commit()
	return num, err
}

func (db *DB) LMclear(keys ...[]byte) (int64, error) {
	t := db.listBatch
	t.Lock()
	defer t.Unlock()

	for _, key := range keys {
		if err := checkKeySize(key); err != nil {
			return 0, err
		}

		db.lDelete(t, key)
		db.rmExpire(t, ListType, key)

	}

	err := t.Commit()
	return int64(len(keys)), err
}

func (db *DB) lFlush() (drop int64, err error) {
	t := db.listBatch
	t.Lock()
	defer t.Unlock()
	return db.flushType(t, ListType)
}

func (db *DB) LExpire(key []byte, duration int64) (int64, error) {
	if duration <= 0 {
		return 0, errExpireValue
	}

	return db.lExpireAt(key, time.Now().Unix()+duration)
}

func (db *DB) LExpireAt(key []byte, when int64) (int64, error) {
	if when <= time.Now().Unix() {
		return 0, errExpireValue
	}

	return db.lExpireAt(key, when)
}

func (db *DB) LTTL(key []byte) (int64, error) {
	if err := checkKeySize(key); err != nil {
		return -1, err
	}

	return db.ttl(ListType, key)
}

func (db *DB) LPersist(key []byte) (int64, error) {
	if err := checkKeySize(key); err != nil {
		return 0, err
	}

	t := db.listBatch
	t.Lock()
	defer t.Unlock()

	n, err := db.rmExpire(t, ListType, key)
	if err != nil {
		return 0, err
	}

	err = t.Commit()
	return n, err
}

func (db *DB) LScan(key []byte, count int, inclusive bool, match string) ([][]byte, error) {
	return db.scan(LMetaType, key, count, inclusive, match)
}

func (db *DB) lEncodeMinKey() []byte {
	return db.lEncodeMetaKey(nil)
}

func (db *DB) lEncodeMaxKey() []byte {
	ek := db.lEncodeMetaKey(nil)
	ek[len(ek)-1] = LMetaType + 1
	return ek
}
<commit_msg>bug fixed<commit_after>package nodb

import (
	"encoding/binary"
	"errors"
	"time"

	"github.com/lunny/nodb/store"
)

const (
	listHeadSeq int32 = 1
	listTailSeq int32 = 2

	listMinSeq     int32 = 1000
	listMaxSeq     int32 = 1<<31 - 1000
	listInitialSeq int32 = listMinSeq + (listMaxSeq-listMinSeq)/2
)

var errLMetaKey = errors.New("invalid lmeta key")
var errListKey = errors.New("invalid list key")
var errListSeq = errors.New("invalid list sequence, overflow")

func (db *DB) lEncodeMetaKey(key []byte) []byte {
	buf := make([]byte, len(key)+2)
	buf[0] = db.index
	buf[1] = LMetaType

	copy(buf[2:], key)
	return buf
}

func (db *DB) lDecodeMetaKey(ek []byte) ([]byte, error) {
	if len(ek) < 2 || ek[0] != db.index || ek[1] != LMetaType {
		return nil, errLMetaKey
	}

	return ek[2:], nil
}

func (db *DB) lEncodeListKey(key []byte, seq int32) []byte {
	buf := make([]byte, len(key)+8)

	pos := 0
	buf[pos] = db.index
	pos++
	buf[pos] = ListType
	pos++

	binary.BigEndian.PutUint16(buf[pos:], uint16(len(key)))
	pos += 2

	copy(buf[pos:], key)
	pos += len(key)

	binary.BigEndian.PutUint32(buf[pos:], uint32(seq))

	return buf
}

func (db *DB) lDecodeListKey(ek []byte) (key []byte, seq int32, err error) {
	if len(ek) < 8 || ek[0] != db.index || ek[1] != ListType {
		err = errListKey
		return
	}

	keyLen := int(binary.BigEndian.Uint16(ek[2:]))
	if keyLen+8 != len(ek) {
		err = errListKey
		return
	}

	key = ek[4 : 4+keyLen]
	seq = int32(binary.BigEndian.Uint32(ek[4+keyLen:]))
	return
}

func (db *DB) lpush(key []byte, whereSeq int32, args ...[]byte) (int64, error) {
	if err := checkKeySize(key); err != nil {
		return 0, err
	}

	var headSeq int32
	var tailSeq int32
	var size int32
	var err error

	t := db.listBatch
	t.Lock()
	defer t.Unlock()

	metaKey := db.lEncodeMetaKey(key)
	headSeq, tailSeq, size, err = db.lGetMeta(nil, metaKey)
	if err != nil {
		return 0, err
	}

	var pushCnt int = len(args)
	if pushCnt == 0 {
		return int64(size), nil
	}

	var seq int32 = headSeq
	var delta int32 = -1
	if whereSeq == listTailSeq {
		seq = tailSeq
		delta = 1
	}

	//	append elements
	if size > 0 {
		seq += delta
	}

	for i := 0; i < pushCnt; i++ {
		ek := db.lEncodeListKey(key, seq+int32(i)*delta)
		t.Put(ek, args[i])
	}

	seq += int32(pushCnt-1) * delta
	if seq <= listMinSeq || seq >= listMaxSeq {
		return 0, errListSeq
	}

	//	set meta info
	if whereSeq == listHeadSeq {
		headSeq = seq
	} else {
		tailSeq = seq
	}

	db.lSetMeta(metaKey, headSeq, tailSeq)

	err = t.Commit()
	return int64(size) + int64(pushCnt), err
}

func (db *DB) lpop(key []byte, whereSeq int32) ([]byte, error) {
	if err := checkKeySize(key); err != nil {
		return nil, err
	}

	t := db.listBatch
	t.Lock()
	defer t.Unlock()

	var headSeq int32
	var tailSeq int32
	var err error

	metaKey := db.lEncodeMetaKey(key)
	headSeq, tailSeq, _, err = db.lGetMeta(nil, metaKey)
	if err != nil {
		return nil, err
	}

	var value []byte

	var seq int32 = headSeq
	if whereSeq == listTailSeq {
		seq = tailSeq
	}

	itemKey := db.lEncodeListKey(key, seq)
	value, err = db.bucket.Get(itemKey)
	if err != nil {
		return nil, err
	}

	if whereSeq == listHeadSeq {
		headSeq += 1
	} else {
		tailSeq -= 1
	}

	t.Delete(itemKey)
	size := db.lSetMeta(metaKey, headSeq, tailSeq)
	if size == 0 {
		db.rmExpire(t, HashType, key)
	}

	err = t.Commit()
	return value, err
}

//	ps : here just focus on deleting the list data,
//		 any other likes expire is ignore.
func (db *DB) lDelete(t *batch, key []byte) int64 {
	mk := db.lEncodeMetaKey(key)

	var headSeq int32
	var tailSeq int32
	var err error

	it := db.bucket.NewIterator()
	defer it.Close()

	headSeq, tailSeq, _, err = db.lGetMeta(it, mk)
	if err != nil {
		return 0
	}

	var num int64 = 0
	startKey := db.lEncodeListKey(key, headSeq)
	stopKey := db.lEncodeListKey(key, tailSeq)

	rit := store.NewRangeIterator(it, &store.Range{startKey, stopKey, store.RangeClose})
	for ; rit.Valid(); rit.Next() {
		t.Delete(rit.RawKey())
		num++
	}

	t.Delete(mk)

	return num
}

func (db *DB) lGetMeta(it *store.Iterator, ek []byte) (headSeq int32, tailSeq int32, size int32, err error) {
	var v []byte
	if it != nil {
		v = it.Find(ek)
	} else {
		v, err = db.bucket.Get(ek)
	}
	if err != nil {
		return
	} else if v == nil {
		headSeq = listInitialSeq
		tailSeq = listInitialSeq
		size = 0
		return
	} else {
		headSeq = int32(binary.LittleEndian.Uint32(v[0:4]))
		tailSeq = int32(binary.LittleEndian.Uint32(v[4:8]))
		size = tailSeq - headSeq + 1
	}
	return
}

func (db *DB) lSetMeta(ek []byte, headSeq int32, tailSeq int32) int32 {
	t := db.listBatch

	var size int32 = tailSeq - headSeq + 1
	if size < 0 {
		//	todo : log error + panic
	} else if size == 0 {
		t.Delete(ek)
	} else {
		buf := make([]byte, 8)

		binary.LittleEndian.PutUint32(buf[0:4], uint32(headSeq))
		binary.LittleEndian.PutUint32(buf[4:8], uint32(tailSeq))

		t.Put(ek, buf)
	}

	return size
}

func (db *DB) lExpireAt(key []byte, when int64) (int64, error) {
	t := db.listBatch
	t.Lock()
	defer t.Unlock()

	if llen, err := db.LLen(key); err != nil || llen == 0 {
		return 0, err
	} else {
		db.expireAt(t, ListType, key, when)
		if err := t.Commit(); err != nil {
			return 0, err
		}
	}
	return 1, nil
}

func (db *DB) LIndex(key []byte, index int32) ([]byte, error) {
	if err := checkKeySize(key); err != nil {
		return nil, err
	}

	var seq int32
	var headSeq int32
	var tailSeq int32
	var err error

	metaKey := db.lEncodeMetaKey(key)

	it := db.bucket.NewIterator()
	defer it.Close()

	headSeq, tailSeq, _, err = db.lGetMeta(it, metaKey)
	if err != nil {
		return nil, err
	}

	if index >= 0 {
		seq = headSeq + index
	} else {
		seq = tailSeq + index + 1
	}

	sk := db.lEncodeListKey(key, seq)
	v := it.Find(sk)

	return v, nil
}

func (db *DB) LLen(key []byte) (int64, error) {
	if err := checkKeySize(key); err != nil {
		return 0, err
	}

	ek := db.lEncodeMetaKey(key)
	_, _, size, err := db.lGetMeta(nil, ek)
	return int64(size), err
}

func (db *DB) LPop(key []byte) ([]byte, error) {
	return db.lpop(key, listHeadSeq)
}

func (db *DB) LPush(key []byte, arg1 []byte, args ...[]byte) (int64, error) {
	var argss = [][]byte{arg1}
	argss = append(argss, args...)
	return db.lpush(key, listHeadSeq, argss...)
}

func (db *DB) LRange(key []byte, start int32, stop int32) ([][]byte, error) {
	if err := checkKeySize(key); err != nil {
		return nil, err
	}

	var headSeq int32
	var llen int32
	var err error

	metaKey := db.lEncodeMetaKey(key)

	it := db.bucket.NewIterator()
	defer it.Close()

	if headSeq, _, llen, err = db.lGetMeta(it, metaKey); err != nil {
		return nil, err
	}

	if start < 0 {
		start = llen + start
	}
	if stop < 0 {
		stop = llen + stop
	}
	if start < 0 {
		start = 0
	}

	if start > stop || start >= llen {
		return [][]byte{}, nil
	}

	if stop >= llen {
		stop = llen - 1
	}

	limit := (stop - start) + 1
	headSeq += start

	v := make([][]byte, 0, limit)

	startKey := db.lEncodeListKey(key, headSeq)
	rit := store.NewRangeLimitIterator(it,
		&store.Range{
			Min:  startKey,
			Max:  nil,
			Type: store.RangeClose},
		&store.Limit{
			Offset: 0,
			Count:  int(limit)})

	for ; rit.Valid(); rit.Next() {
		v = append(v, rit.Value())
	}

	return v, nil
}

func (db *DB) RPop(key []byte) ([]byte, error) {
	return db.lpop(key, listTailSeq)
}

func (db *DB) RPush(key []byte, arg1 []byte, args ...[]byte) (int64, error) {
	var argss = [][]byte{arg1}
	argss = append(argss, args...)
	return db.lpush(key, listTailSeq, argss...)
}

func (db *DB) LClear(key []byte) (int64, error) {
	if err := checkKeySize(key); err != nil {
		return 0, err
	}

	t := db.listBatch
	t.Lock()
	defer t.Unlock()

	num := db.lDelete(t, key)
	db.rmExpire(t, ListType, key)

	err := t.Commit()
	return num, err
}

func (db *DB) LMclear(keys ...[]byte) (int64, error) {
	t := db.listBatch
	t.Lock()
	defer t.Unlock()

	for _, key := range keys {
		if err := checkKeySize(key); err != nil {
			return 0, err
		}

		db.lDelete(t, key)
		db.rmExpire(t, ListType, key)

	}

	err := t.Commit()
	return int64(len(keys)), err
}

func (db *DB) lFlush() (drop int64, err error) {
	t := db.listBatch
	t.Lock()
	defer t.Unlock()
	return db.flushType(t, ListType)
}

func (db *DB) LExpire(key []byte, duration int64) (int64, error) {
	if duration <= 0 {
		return 0, errExpireValue
	}

	return db.lExpireAt(key, time.Now().Unix()+duration)
}

func (db *DB) LExpireAt(key []byte, when int64) (int64, error) {
	if when <= time.Now().Unix() {
		return 0, errExpireValue
	}

	return db.lExpireAt(key, when)
}

func (db *DB) LTTL(key []byte) (int64, error) {
	if err := checkKeySize(key); err != nil {
		return -1, err
	}

	return db.ttl(ListType, key)
}

func (db *DB) LPersist(key []byte) (int64, error) {
	if err := checkKeySize(key); err != nil {
		return 0, err
	}

	t := db.listBatch
	t.Lock()
	defer t.Unlock()

	n, err := db.rmExpire(t, ListType, key)
	if err != nil {
		return 0, err
	}

	err = t.Commit()
	return n, err
}

func (db *DB) LScan(key []byte, count int, inclusive bool, match string) ([][]byte, error) {
	return db.scan(LMetaType, key, count, inclusive, match)
}

func (db *DB) lEncodeMinKey() []byte {
	return db.lEncodeMetaKey(nil)
}

func (db *DB) lEncodeMaxKey() []byte {
	ek := db.lEncodeMetaKey(nil)
	ek[len(ek)-1] = LMetaType + 1
	return ek
}
<|endoftext|>


<commit_before>package reform

import (
	"database/sql"
	"fmt"
	"reflect"
	"time"
)

// Querier performs queries and commands.
type Querier struct {
	dbtx DBTX
	tag  string
	Dialect
	Logger         Logger
	dbForCallbacks *DB
}

func newQuerier(dbtx DBTX, dialect Dialect, logger Logger, dbForCallbacks *DB) *Querier {
	return &Querier{
		dbtx:           dbtx,
		Dialect:        dialect,
		Logger:         logger,
		dbForCallbacks: dbForCallbacks,
	}
}

func (q *Querier) logBefore(query string, args []interface{}) {
	if q.Logger != nil {
		q.Logger.Before(query, args)
	}
}

func (q *Querier) logAfter(query string, args []interface{}, d time.Duration, err error) {
	if q.Logger != nil {
		q.Logger.After(query, args, d, err)
	}
}

func (q *Querier) callStructMethod(str Struct, methodName string) error {
	if method := reflect.ValueOf(str).MethodByName("AfterFind"); method.IsValid() {
		switch f := method.Interface().(type) {
		case func():
			f()

		case func(*DB):
			f(q.dbForCallbacks)

		case func(*Querier):
			f(q)

		case func(interface{}): // For compatibility with other ORMs
			f(q.dbForCallbacks)

		case func() error:
			return f()

		case func(*DB) error:
			return f(q.dbForCallbacks)

		case func(*Querier) error:
			return f(q)

		case func(interface{}) error: // For compatibility with other ORMS
			return f(q.dbForCallbacks)

		default:
			// TODO: Response by an error
		}
	}
	return nil
}

func (q *Querier) startQuery(command string) string {
	if q.tag == "" {
		return command
	}
	return command + " /* " + q.tag + " */"
}

// WithTag returns a copy of Querier with set tag. Returned Querier is tied to the same DB or TX.
// See Tagging section in documentation for details.
func (q *Querier) WithTag(format string, a ...interface{}) *Querier {
	newQ := newQuerier(q.dbtx, q.Dialect, q.Logger, q.dbForCallbacks)
	if len(a) == 0 {
		newQ.tag = format
	} else {
		newQ.tag = fmt.Sprintf(format, a...)
	}
	return newQ
}

// QualifiedView returns quoted qualified view name.
func (q *Querier) QualifiedView(view View) string {
	v := q.QuoteIdentifier(view.Name())
	if view.Schema() != "" {
		v = q.QuoteIdentifier(view.Schema()) + "." + v
	}
	return v
}

// QualifiedColumns returns a slice of quoted qualified column names for given view.
func (q *Querier) QualifiedColumns(view View) []string {
	v := q.QualifiedView(view)
	res := view.Columns()
	for i := 0; i < len(res); i++ {
		res[i] = v + "." + q.QuoteIdentifier(res[i])
	}
	return res
}

// Exec executes a query without returning any rows.
// The args are for any placeholder parameters in the query.
func (q *Querier) Exec(query string, args ...interface{}) (sql.Result, error) {
	q.logBefore(query, args)
	start := time.Now()
	res, err := q.dbtx.Exec(query, args...)
	q.logAfter(query, args, time.Since(start), err)
	return res, err
}

// Query executes a query that returns rows, typically a SELECT.
// The args are for any placeholder parameters in the query.
func (q *Querier) Query(query string, args ...interface{}) (*sql.Rows, error) {
	q.logBefore(query, args)
	start := time.Now()
	rows, err := q.dbtx.Query(query, args...)
	q.logAfter(query, args, time.Since(start), err)
	return rows, err
}

// QueryRow executes a query that is expected to return at most one row.
// QueryRow always returns a non-nil value. Errors are deferred until Row's Scan method is called.
func (q *Querier) QueryRow(query string, args ...interface{}) *sql.Row {
	q.logBefore(query, args)
	start := time.Now()
	row := q.dbtx.QueryRow(query, args...)
	q.logAfter(query, args, time.Since(start), nil)
	return row
}

// check interface
var _ DBTX = (*Querier)(nil)
<commit_msg>Fixed a typo: was <"AfterFind"> instead of <methodName><commit_after>package reform

import (
	"database/sql"
	"fmt"
	"reflect"
	"time"
)

// Querier performs queries and commands.
type Querier struct {
	dbtx DBTX
	tag  string
	Dialect
	Logger         Logger
	dbForCallbacks *DB
}

func newQuerier(dbtx DBTX, dialect Dialect, logger Logger, dbForCallbacks *DB) *Querier {
	return &Querier{
		dbtx:           dbtx,
		Dialect:        dialect,
		Logger:         logger,
		dbForCallbacks: dbForCallbacks,
	}
}

func (q *Querier) logBefore(query string, args []interface{}) {
	if q.Logger != nil {
		q.Logger.Before(query, args)
	}
}

func (q *Querier) logAfter(query string, args []interface{}, d time.Duration, err error) {
	if q.Logger != nil {
		q.Logger.After(query, args, d, err)
	}
}

func (q *Querier) callStructMethod(str Struct, methodName string) error {
	if method := reflect.ValueOf(str).MethodByName(methodName); method.IsValid() {
		switch f := method.Interface().(type) {
		case func():
			f()

		case func(*DB):
			f(q.dbForCallbacks)

		case func(*Querier):
			f(q)

		case func(interface{}): // For compatibility with other ORMs
			f(q.dbForCallbacks)

		case func() error:
			return f()

		case func(*DB) error:
			return f(q.dbForCallbacks)

		case func(*Querier) error:
			return f(q)

		case func(interface{}) error: // For compatibility with other ORMS
			return f(q.dbForCallbacks)

		default:
			// TODO: Response by an error
		}
	}
	return nil
}

func (q *Querier) startQuery(command string) string {
	if q.tag == "" {
		return command
	}
	return command + " /* " + q.tag + " */"
}

// WithTag returns a copy of Querier with set tag. Returned Querier is tied to the same DB or TX.
// See Tagging section in documentation for details.
func (q *Querier) WithTag(format string, a ...interface{}) *Querier {
	newQ := newQuerier(q.dbtx, q.Dialect, q.Logger, q.dbForCallbacks)
	if len(a) == 0 {
		newQ.tag = format
	} else {
		newQ.tag = fmt.Sprintf(format, a...)
	}
	return newQ
}

// QualifiedView returns quoted qualified view name.
func (q *Querier) QualifiedView(view View) string {
	v := q.QuoteIdentifier(view.Name())
	if view.Schema() != "" {
		v = q.QuoteIdentifier(view.Schema()) + "." + v
	}
	return v
}

// QualifiedColumns returns a slice of quoted qualified column names for given view.
func (q *Querier) QualifiedColumns(view View) []string {
	v := q.QualifiedView(view)
	res := view.Columns()
	for i := 0; i < len(res); i++ {
		res[i] = v + "." + q.QuoteIdentifier(res[i])
	}
	return res
}

// Exec executes a query without returning any rows.
// The args are for any placeholder parameters in the query.
func (q *Querier) Exec(query string, args ...interface{}) (sql.Result, error) {
	q.logBefore(query, args)
	start := time.Now()
	res, err := q.dbtx.Exec(query, args...)
	q.logAfter(query, args, time.Since(start), err)
	return res, err
}

// Query executes a query that returns rows, typically a SELECT.
// The args are for any placeholder parameters in the query.
func (q *Querier) Query(query string, args ...interface{}) (*sql.Rows, error) {
	q.logBefore(query, args)
	start := time.Now()
	rows, err := q.dbtx.Query(query, args...)
	q.logAfter(query, args, time.Since(start), err)
	return rows, err
}

// QueryRow executes a query that is expected to return at most one row.
// QueryRow always returns a non-nil value. Errors are deferred until Row's Scan method is called.
func (q *Querier) QueryRow(query string, args ...interface{}) *sql.Row {
	q.logBefore(query, args)
	start := time.Now()
	row := q.dbtx.QueryRow(query, args...)
	q.logAfter(query, args, time.Since(start), nil)
	return row
}

// check interface
var _ DBTX = (*Querier)(nil)
<|endoftext|>


<commit_before>package test

import (
	"bytes"
	"fmt"
	"math/rand"
	"net/http"
	"os"
	"syscall"
	"testing"
	"time"

	"github.com/coreos/etcd/third_party/github.com/coreos/go-etcd/etcd"

	"github.com/coreos/etcd/tests"
	"github.com/coreos/etcd/third_party/github.com/stretchr/testify/assert"
)

// remove the node and node rejoin with previous log
func TestRemoveNode(t *testing.T) {
	procAttr := new(os.ProcAttr)
	procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}

	clusterSize := 4
	argGroup, etcds, _ := CreateCluster(clusterSize, procAttr, false)
	defer DestroyCluster(etcds)

	time.Sleep(time.Second)

	c := etcd.NewClient(nil)

	c.SyncCluster()

	resp, _ := tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":4, "syncInterval":1}`))
	if !assert.Equal(t, resp.StatusCode, 200) {
		t.FailNow()
	}

	rmReq, _ := http.NewRequest("DELETE", "http://127.0.0.1:7001/remove/node3", nil)

	client := &http.Client{}
	for i := 0; i < 2; i++ {
		for i := 0; i < 2; i++ {
			r, _ := tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":3}`))
			if !assert.Equal(t, r.StatusCode, 200) {
				t.FailNow()
			}

			client.Do(rmReq)

			fmt.Println("send remove to node3 and wait for its exiting")
			time.Sleep(100 * time.Millisecond)

			resp, err := c.Get("_etcd/machines", false, false)

			if err != nil {
				panic(err)
			}

			if len(resp.Node.Nodes) != 3 {
				t.Fatal("cannot remove peer")
			}

			etcds[2].Kill()
			etcds[2].Wait()

			if i == 1 {
				// rejoin with log
				etcds[2], err = os.StartProcess(EtcdBinPath, argGroup[2], procAttr)
			} else {
				// rejoin without log
				etcds[2], err = os.StartProcess(EtcdBinPath, append(argGroup[2], "-f"), procAttr)
			}

			if err != nil {
				panic(err)
			}

			r, _ = tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":4}`))
			if !assert.Equal(t, r.StatusCode, 200) {
				t.FailNow()
			}

			time.Sleep(time.Second + time.Second)

			resp, err = c.Get("_etcd/machines", false, false)

			if err != nil {
				panic(err)
			}

			if len(resp.Node.Nodes) != 4 {
				t.Fatalf("add peer fails #1 (%d != 4)", len(resp.Node.Nodes))
			}
		}

		// first kill the node, then remove it, then add it back
		for i := 0; i < 2; i++ {
			r, _ := tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":3}`))
			if !assert.Equal(t, r.StatusCode, 200) {
				t.FailNow()
			}

			etcds[2].Kill()
			fmt.Println("kill node3 and wait for its exiting")
			etcds[2].Wait()

			client.Do(rmReq)

			time.Sleep(100 * time.Millisecond)

			resp, err := c.Get("_etcd/machines", false, false)

			if err != nil {
				panic(err)
			}

			if len(resp.Node.Nodes) != 3 {
				t.Fatal("cannot remove peer")
			}

			if i == 1 {
				// rejoin with log
				etcds[2], err = os.StartProcess(EtcdBinPath, append(argGroup[2]), procAttr)
			} else {
				// rejoin without log
				etcds[2], err = os.StartProcess(EtcdBinPath, append(argGroup[2], "-f"), procAttr)
			}

			if err != nil {
				panic(err)
			}

			r, _ = tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":4}`))
			if !assert.Equal(t, r.StatusCode, 200) {
				t.FailNow()
			}

			time.Sleep(time.Second + time.Second)

			resp, err = c.Get("_etcd/machines", false, false)

			if err != nil {
				panic(err)
			}

			if len(resp.Node.Nodes) != 4 {
				t.Fatalf("add peer fails #2 (%d != 4)", len(resp.Node.Nodes))
			}
		}
	}
}

func TestRemovePausedNode(t *testing.T) {
	procAttr := new(os.ProcAttr)
	procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}

	clusterSize := 4
	_, etcds, _ := CreateCluster(clusterSize, procAttr, false)
	defer DestroyCluster(etcds)

	time.Sleep(time.Second)

	c := etcd.NewClient(nil)

	c.SyncCluster()

	r, _ := tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":3, "removeDelay":1, "syncInterval":1}`))
	if !assert.Equal(t, r.StatusCode, 200) {
		t.FailNow()
	}
	time.Sleep(2 * time.Second)

	resp, err := c.Get("_etcd/machines", false, false)
	if err != nil {
		panic(err)
	}
	if len(resp.Node.Nodes) != 3 {
		t.Fatal("cannot remove peer")
	}

	for i := 0; i < clusterSize; i++ {
		// first pause the node, then remove it, then resume it
		idx := rand.Int() % clusterSize

		etcds[idx].Signal(syscall.SIGSTOP)
		fmt.Printf("pause node%d and let standby node take its place\n", idx+1)

		time.Sleep(4 * time.Second)

		etcds[idx].Signal(syscall.SIGCONT)
		// let it change its state to candidate at least
		time.Sleep(time.Second)

		stop := make(chan bool)
		leaderChan := make(chan string, 1)
		all := make(chan bool, 1)

		go Monitor(clusterSize, clusterSize, leaderChan, all, stop)
		<-all
		<-leaderChan
		stop <- true

		resp, err = c.Get("_etcd/machines", false, false)
		if err != nil {
			panic(err)
		}
		if len(resp.Node.Nodes) != 3 {
			t.Fatalf("add peer fails (%d != 3)", len(resp.Node.Nodes))
		}
		for i := 0; i < 3; i++ {
			if resp.Node.Nodes[i].Key == fmt.Sprintf("node%d", idx+1) {
				t.Fatal("node should be removed")
			}
		}
	}
}
<commit_msg>fix(remove_node_test): ensure cluster config is activated<commit_after>package test

import (
	"bytes"
	"fmt"
	"math/rand"
	"net/http"
	"os"
	"syscall"
	"testing"
	"time"

	"github.com/coreos/etcd/third_party/github.com/coreos/go-etcd/etcd"

	"github.com/coreos/etcd/tests"
	"github.com/coreos/etcd/third_party/github.com/stretchr/testify/assert"
)

// remove the node and node rejoin with previous log
func TestRemoveNode(t *testing.T) {
	procAttr := new(os.ProcAttr)
	procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}

	clusterSize := 4
	argGroup, etcds, _ := CreateCluster(clusterSize, procAttr, false)
	defer DestroyCluster(etcds)

	time.Sleep(time.Second)

	c := etcd.NewClient(nil)

	c.SyncCluster()

	resp, _ := tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":4, "syncInterval":1}`))
	if !assert.Equal(t, resp.StatusCode, 200) {
		t.FailNow()
	}

	rmReq, _ := http.NewRequest("DELETE", "http://127.0.0.1:7001/remove/node3", nil)

	client := &http.Client{}
	for i := 0; i < 2; i++ {
		for i := 0; i < 2; i++ {
			r, _ := tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":3}`))
			if !assert.Equal(t, r.StatusCode, 200) {
				t.FailNow()
			}

			client.Do(rmReq)

			fmt.Println("send remove to node3 and wait for its exiting")
			time.Sleep(100 * time.Millisecond)

			resp, err := c.Get("_etcd/machines", false, false)

			if err != nil {
				panic(err)
			}

			if len(resp.Node.Nodes) != 3 {
				t.Fatal("cannot remove peer")
			}

			etcds[2].Kill()
			etcds[2].Wait()

			if i == 1 {
				// rejoin with log
				etcds[2], err = os.StartProcess(EtcdBinPath, argGroup[2], procAttr)
			} else {
				// rejoin without log
				etcds[2], err = os.StartProcess(EtcdBinPath, append(argGroup[2], "-f"), procAttr)
			}

			if err != nil {
				panic(err)
			}

			r, _ = tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":4}`))
			if !assert.Equal(t, r.StatusCode, 200) {
				t.FailNow()
			}

			time.Sleep(time.Second + time.Second)

			resp, err = c.Get("_etcd/machines", false, false)

			if err != nil {
				panic(err)
			}

			if len(resp.Node.Nodes) != 4 {
				t.Fatalf("add peer fails #1 (%d != 4)", len(resp.Node.Nodes))
			}
		}

		// first kill the node, then remove it, then add it back
		for i := 0; i < 2; i++ {
			r, _ := tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":3}`))
			if !assert.Equal(t, r.StatusCode, 200) {
				t.FailNow()
			}

			etcds[2].Kill()
			fmt.Println("kill node3 and wait for its exiting")
			etcds[2].Wait()

			client.Do(rmReq)

			time.Sleep(100 * time.Millisecond)

			resp, err := c.Get("_etcd/machines", false, false)

			if err != nil {
				panic(err)
			}

			if len(resp.Node.Nodes) != 3 {
				t.Fatal("cannot remove peer")
			}

			if i == 1 {
				// rejoin with log
				etcds[2], err = os.StartProcess(EtcdBinPath, append(argGroup[2]), procAttr)
			} else {
				// rejoin without log
				etcds[2], err = os.StartProcess(EtcdBinPath, append(argGroup[2], "-f"), procAttr)
			}

			if err != nil {
				panic(err)
			}

			r, _ = tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":4}`))
			if !assert.Equal(t, r.StatusCode, 200) {
				t.FailNow()
			}

			time.Sleep(time.Second + time.Second)

			resp, err = c.Get("_etcd/machines", false, false)

			if err != nil {
				panic(err)
			}

			if len(resp.Node.Nodes) != 4 {
				t.Fatalf("add peer fails #2 (%d != 4)", len(resp.Node.Nodes))
			}
		}
	}
}

func TestRemovePausedNode(t *testing.T) {
	procAttr := new(os.ProcAttr)
	procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}

	clusterSize := 4
	_, etcds, _ := CreateCluster(clusterSize, procAttr, false)
	defer DestroyCluster(etcds)

	time.Sleep(time.Second)

	c := etcd.NewClient(nil)

	c.SyncCluster()

	r, _ := tests.Put("http://localhost:7001/v2/admin/config", "application/json", bytes.NewBufferString(`{"activeSize":3, "removeDelay":1, "syncInterval":1}`))
	if !assert.Equal(t, r.StatusCode, 200) {
		t.FailNow()
	}
	// Wait for standby instances to update its cluster config
	time.Sleep(6 * time.Second)

	resp, err := c.Get("_etcd/machines", false, false)
	if err != nil {
		panic(err)
	}
	if len(resp.Node.Nodes) != 3 {
		t.Fatal("cannot remove peer")
	}

	for i := 0; i < clusterSize; i++ {
		// first pause the node, then remove it, then resume it
		idx := rand.Int() % clusterSize

		etcds[idx].Signal(syscall.SIGSTOP)
		fmt.Printf("pause node%d and let standby node take its place\n", idx+1)

		time.Sleep(4 * time.Second)

		etcds[idx].Signal(syscall.SIGCONT)
		// let it change its state to candidate at least
		time.Sleep(time.Second)

		stop := make(chan bool)
		leaderChan := make(chan string, 1)
		all := make(chan bool, 1)

		go Monitor(clusterSize, clusterSize, leaderChan, all, stop)
		<-all
		<-leaderChan
		stop <- true

		resp, err = c.Get("_etcd/machines", false, false)
		if err != nil {
			panic(err)
		}
		if len(resp.Node.Nodes) != 3 {
			t.Fatalf("add peer fails (%d != 3)", len(resp.Node.Nodes))
		}
		for i := 0; i < 3; i++ {
			if resp.Node.Nodes[i].Key == fmt.Sprintf("node%d", idx+1) {
				t.Fatal("node should be removed")
			}
		}
	}
}
<|endoftext|>


<commit_before>package h2spec

import (
	"github.com/bradfitz/http2"
	"github.com/bradfitz/http2/hpack"
)

func HttpRequestResponseExchangeTestGroup() *TestGroup {
	tg := NewTestGroup("8.1", "HTTP Request/Response Exchange")

	tg.AddTestGroup(HttpHeaderFieldsTestGroup())

	return tg
}

func HttpHeaderFieldsTestGroup() *TestGroup {
	tg := NewTestGroup("8.1.2", "HTTP Header Fields")

	tg.AddTestCase(NewTestCase(
		"Sends a HEADERS frame that contains the header field name in uppercase letters",
		"The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.",
		func(ctx *Context) (expected []Result, actual Result) {
			http2Conn := CreateHttp2Conn(ctx, true)
			defer http2Conn.conn.Close()

			hdrs := []hpack.HeaderField{
				pair(":method", "GET"),
				pair(":scheme", "http"),
				pair(":path", "/"),
				pair(":authority", ctx.Authority()),
				pair("X-TEST", "test"),
			}

			var hp http2.HeadersFrameParam
			hp.StreamID = 1
			hp.EndStream = true
			hp.EndHeaders = true
			hp.BlockFragment = http2Conn.EncodeHeader(hdrs)
			http2Conn.fr.WriteHeaders(hp)

			actualCodes := []http2.ErrCode{http2.ErrCodeProtocol}
			return TestStreamError(ctx, http2Conn, actualCodes)
		},
	))

	tg.AddTestGroup(PseudoHeaderFieldsTestGroup())
	tg.AddTestGroup(ConnectionSpecificHeaderFieldsTestGroup())
	tg.AddTestGroup(RequestPseudoHeaderFieldsTestGroup())
	tg.AddTestGroup(MalformedRequestsAndResponsesTestGroup())

	return tg
}

func PseudoHeaderFieldsTestGroup() *TestGroup {
	tg := NewTestGroup("8.1.2.1", "Pseudo-Header Fields")

	tg.AddTestCase(NewTestCase(
		"Sends a HEADERS frame that contains the pseudo-header field defined for response",
		"The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.",
		func(ctx *Context) (expected []Result, actual Result) {
			http2Conn := CreateHttp2Conn(ctx, true)
			defer http2Conn.conn.Close()

			hdrs := []hpack.HeaderField{
				pair(":method", "GET"),
				pair(":scheme", "http"),
				pair(":path", "/"),
				pair(":authority", ctx.Authority()),
				pair(":status", "200"),
			}

			var hp http2.HeadersFrameParam
			hp.StreamID = 1
			hp.EndStream = true
			hp.EndHeaders = true
			hp.BlockFragment = http2Conn.EncodeHeader(hdrs)
			http2Conn.fr.WriteHeaders(hp)

			actualCodes := []http2.ErrCode{http2.ErrCodeProtocol}
			return TestStreamError(ctx, http2Conn, actualCodes)
		},
	))

	tg.AddTestCase(NewTestCase(
		"Sends a HEADERS frame that contains the invalid pseudo-header field",
		"The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.",
		func(ctx *Context) (expected []Result, actual Result) {
			http2Conn := CreateHttp2Conn(ctx, true)
			defer http2Conn.conn.Close()

			hdrs := []hpack.HeaderField{
				pair(":method", "GET"),
				pair(":scheme", "http"),
				pair(":path", "/"),
				pair(":authority", ctx.Authority()),
				pair(":test", "test"),
			}

			var hp http2.HeadersFrameParam
			hp.StreamID = 1
			hp.EndStream = true
			hp.EndHeaders = true
			hp.BlockFragment = http2Conn.EncodeHeader(hdrs)
			http2Conn.fr.WriteHeaders(hp)

			actualCodes := []http2.ErrCode{http2.ErrCodeProtocol}
			return TestStreamError(ctx, http2Conn, actualCodes)
		},
	))

	tg.AddTestCase(NewTestCase(
		"Sends a HEADERS frame that contains a pseudo-header field that appears in a header block after a regular header field",
		"The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.",
		func(ctx *Context) (expected []Result, actual Result) {
			http2Conn := CreateHttp2Conn(ctx, true)
			defer http2Conn.conn.Close()

			hdrs := []hpack.HeaderField{
				pair("x-test", "test"),
				pair(":method", "GET"),
				pair(":scheme", "http"),
				pair(":path", "/"),
				pair(":authority", ctx.Authority()),
			}

			var hp http2.HeadersFrameParam
			hp.StreamID = 1
			hp.EndStream = true
			hp.EndHeaders = true
			hp.BlockFragment = http2Conn.EncodeHeader(hdrs)
			http2Conn.fr.WriteHeaders(hp)

			actualCodes := []http2.ErrCode{http2.ErrCodeProtocol}
			return TestStreamError(ctx, http2Conn, actualCodes)
		},
	))

	return tg
}

func ConnectionSpecificHeaderFieldsTestGroup() *TestGroup {
	tg := NewTestGroup("8.1.2.2", "Connection-Specific Header Fields")

	tg.AddTestCase(NewTestCase(
		"Sends a HEADERS frame that contains the connection-specific header field",
		"The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.",
		func(ctx *Context) (expected []Result, actual Result) {
			http2Conn := CreateHttp2Conn(ctx, true)
			defer http2Conn.conn.Close()

			hdrs := []hpack.HeaderField{
				pair(":method", "GET"),
				pair(":scheme", "http"),
				pair(":path", "/"),
				pair(":authority", ctx.Authority()),
				pair("connection", "keep-alive"),
			}

			var hp http2.HeadersFrameParam
			hp.StreamID = 1
			hp.EndStream = true
			hp.EndHeaders = true
			hp.BlockFragment = http2Conn.EncodeHeader(hdrs)
			http2Conn.fr.WriteHeaders(hp)

			actualCodes := []http2.ErrCode{http2.ErrCodeProtocol}
			return TestStreamError(ctx, http2Conn, actualCodes)
		},
	))

	tg.AddTestCase(NewTestCase(
		"Sends a HEADERS frame that contains the TE header field that contain any value other than \"trailers\"",
		"The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.",
		func(ctx *Context) (expected []Result, actual Result) {
			http2Conn := CreateHttp2Conn(ctx, true)
			defer http2Conn.conn.Close()

			hdrs := []hpack.HeaderField{
				pair(":method", "GET"),
				pair(":scheme", "http"),
				pair(":path", "/"),
				pair(":authority", ctx.Authority()),
				pair("trailers", "test"),
				pair("te", "trailers, deflate"),
			}

			var hp http2.HeadersFrameParam
			hp.StreamID = 1
			hp.EndStream = true
			hp.EndHeaders = true
			hp.BlockFragment = http2Conn.EncodeHeader(hdrs)
			http2Conn.fr.WriteHeaders(hp)

			actualCodes := []http2.ErrCode{http2.ErrCodeProtocol}
			return TestStreamError(ctx, http2Conn, actualCodes)
		},
	))

	return tg
}

func RequestPseudoHeaderFieldsTestGroup() *TestGroup {
	tg := NewTestGroup("8.1.2.3", "Request Pseudo-Header Fields")

	tg.AddTestCase(NewTestCase(
		"Sends a HEADERS frame that is omitted mandatory pseudo-header fields",
		"The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.",
		func(ctx *Context) (expected []Result, actual Result) {
			http2Conn := CreateHttp2Conn(ctx, true)
			defer http2Conn.conn.Close()

			hdrs := []hpack.HeaderField{
				pair(":method", "GET"),
				pair(":scheme", "http"),
				pair(":authority", ctx.Authority()),
			}

			var hp http2.HeadersFrameParam
			hp.StreamID = 1
			hp.EndStream = true
			hp.EndHeaders = true
			hp.BlockFragment = http2Conn.EncodeHeader(hdrs)
			http2Conn.fr.WriteHeaders(hp)

			actualCodes := []http2.ErrCode{http2.ErrCodeProtocol}
			return TestStreamError(ctx, http2Conn, actualCodes)
		},
	))

	tg.AddTestCase(NewTestCase(
		"Sends a HEADERS frame containing more than one pseudo-header fields with the same name",
		"The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.",
		func(ctx *Context) (expected []Result, actual Result) {
			http2Conn := CreateHttp2Conn(ctx, true)
			defer http2Conn.conn.Close()

			hdrs := []hpack.HeaderField{
				pair(":method", "GET"),
				pair(":scheme", "http"),
				pair(":path", "http"),
				pair(":authority", ctx.Authority()),
				pair(":method", "GET"),
				pair(":scheme", "http"),
				pair(":path", "http"),
				pair(":authority", ctx.Authority()),
			}

			var hp http2.HeadersFrameParam
			hp.StreamID = 1
			hp.EndStream = true
			hp.EndHeaders = true
			hp.BlockFragment = http2Conn.EncodeHeader(hdrs)
			http2Conn.fr.WriteHeaders(hp)

			actualCodes := []http2.ErrCode{http2.ErrCodeProtocol}
			return TestStreamError(ctx, http2Conn, actualCodes)
		},
	))

	return tg
}

func MalformedRequestsAndResponsesTestGroup() *TestGroup {
	tg := NewTestGroup("8.1.2.6", "Malformed Requests and Responses")

	tg.AddTestCase(NewTestCase(
		"Sends a HEADERS frame that contains the \"content-length\" header field which does not equal the sum of the DATA frame payload lengths",
		"The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.",
		func(ctx *Context) (expected []Result, actual Result) {
			http2Conn := CreateHttp2Conn(ctx, true)
			defer http2Conn.conn.Close()

			hdrs := []hpack.HeaderField{
				pair(":method", "POST"),
				pair(":scheme", "http"),
				pair(":path", "/"),
				pair(":authority", ctx.Authority()),
				pair("content-length", "1"),
			}

			var hp http2.HeadersFrameParam
			hp.StreamID = 1
			hp.EndStream = false
			hp.EndHeaders = true
			hp.BlockFragment = http2Conn.EncodeHeader(hdrs)
			http2Conn.fr.WriteHeaders(hp)
			http2Conn.fr.WriteData(1, true, []byte("test"))

			actualCodes := []http2.ErrCode{http2.ErrCodeProtocol}
			return TestStreamError(ctx, http2Conn, actualCodes)
		},
	))

	tg.AddTestCase(NewTestCase(
		"Sends a HEADERS frame that contains the \"content-length\" header field which does not equal the sum of the multiple DATA frame payload lengths",
		"The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.",
		func(ctx *Context) (expected []Result, actual Result) {
			http2Conn := CreateHttp2Conn(ctx, true)
			defer http2Conn.conn.Close()

			hdrs := []hpack.HeaderField{
				pair(":method", "POST"),
				pair(":scheme", "http"),
				pair(":path", "/"),
				pair(":authority", ctx.Authority()),
				pair("content-length", "1"),
			}

			var hp http2.HeadersFrameParam
			hp.StreamID = 1
			hp.EndStream = false
			hp.EndHeaders = true
			hp.BlockFragment = http2Conn.EncodeHeader(hdrs)
			http2Conn.fr.WriteHeaders(hp)
			http2Conn.fr.WriteData(1, false, []byte("test"))
			http2Conn.fr.WriteData(1, true, []byte("test"))

			actualCodes := []http2.ErrCode{http2.ErrCodeProtocol}
			return TestStreamError(ctx, http2Conn, actualCodes)
		},
	))

	return tg
}
<commit_msg>Fix description of 8.1.2.3<commit_after>package h2spec

import (
	"github.com/bradfitz/http2"
	"github.com/bradfitz/http2/hpack"
)

func HttpRequestResponseExchangeTestGroup() *TestGroup {
	tg := NewTestGroup("8.1", "HTTP Request/Response Exchange")

	tg.AddTestGroup(HttpHeaderFieldsTestGroup())

	return tg
}

func HttpHeaderFieldsTestGroup() *TestGroup {
	tg := NewTestGroup("8.1.2", "HTTP Header Fields")

	tg.AddTestCase(NewTestCase(
		"Sends a HEADERS frame that contains the header field name in uppercase letters",
		"The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.",
		func(ctx *Context) (expected []Result, actual Result) {
			http2Conn := CreateHttp2Conn(ctx, true)
			defer http2Conn.conn.Close()

			hdrs := []hpack.HeaderField{
				pair(":method", "GET"),
				pair(":scheme", "http"),
				pair(":path", "/"),
				pair(":authority", ctx.Authority()),
				pair("X-TEST", "test"),
			}

			var hp http2.HeadersFrameParam
			hp.StreamID = 1
			hp.EndStream = true
			hp.EndHeaders = true
			hp.BlockFragment = http2Conn.EncodeHeader(hdrs)
			http2Conn.fr.WriteHeaders(hp)

			actualCodes := []http2.ErrCode{http2.ErrCodeProtocol}
			return TestStreamError(ctx, http2Conn, actualCodes)
		},
	))

	tg.AddTestGroup(PseudoHeaderFieldsTestGroup())
	tg.AddTestGroup(ConnectionSpecificHeaderFieldsTestGroup())
	tg.AddTestGroup(RequestPseudoHeaderFieldsTestGroup())
	tg.AddTestGroup(MalformedRequestsAndResponsesTestGroup())

	return tg
}

func PseudoHeaderFieldsTestGroup() *TestGroup {
	tg := NewTestGroup("8.1.2.1", "Pseudo-Header Fields")

	tg.AddTestCase(NewTestCase(
		"Sends a HEADERS frame that contains the pseudo-header field defined for response",
		"The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.",
		func(ctx *Context) (expected []Result, actual Result) {
			http2Conn := CreateHttp2Conn(ctx, true)
			defer http2Conn.conn.Close()

			hdrs := []hpack.HeaderField{
				pair(":method", "GET"),
				pair(":scheme", "http"),
				pair(":path", "/"),
				pair(":authority", ctx.Authority()),
				pair(":status", "200"),
			}

			var hp http2.HeadersFrameParam
			hp.StreamID = 1
			hp.EndStream = true
			hp.EndHeaders = true
			hp.BlockFragment = http2Conn.EncodeHeader(hdrs)
			http2Conn.fr.WriteHeaders(hp)

			actualCodes := []http2.ErrCode{http2.ErrCodeProtocol}
			return TestStreamError(ctx, http2Conn, actualCodes)
		},
	))

	tg.AddTestCase(NewTestCase(
		"Sends a HEADERS frame that contains the invalid pseudo-header field",
		"The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.",
		func(ctx *Context) (expected []Result, actual Result) {
			http2Conn := CreateHttp2Conn(ctx, true)
			defer http2Conn.conn.Close()

			hdrs := []hpack.HeaderField{
				pair(":method", "GET"),
				pair(":scheme", "http"),
				pair(":path", "/"),
				pair(":authority", ctx.Authority()),
				pair(":test", "test"),
			}

			var hp http2.HeadersFrameParam
			hp.StreamID = 1
			hp.EndStream = true
			hp.EndHeaders = true
			hp.BlockFragment = http2Conn.EncodeHeader(hdrs)
			http2Conn.fr.WriteHeaders(hp)

			actualCodes := []http2.ErrCode{http2.ErrCodeProtocol}
			return TestStreamError(ctx, http2Conn, actualCodes)
		},
	))

	tg.AddTestCase(NewTestCase(
		"Sends a HEADERS frame that contains a pseudo-header field that appears in a header block after a regular header field",
		"The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.",
		func(ctx *Context) (expected []Result, actual Result) {
			http2Conn := CreateHttp2Conn(ctx, true)
			defer http2Conn.conn.Close()

			hdrs := []hpack.HeaderField{
				pair("x-test", "test"),
				pair(":method", "GET"),
				pair(":scheme", "http"),
				pair(":path", "/"),
				pair(":authority", ctx.Authority()),
			}

			var hp http2.HeadersFrameParam
			hp.StreamID = 1
			hp.EndStream = true
			hp.EndHeaders = true
			hp.BlockFragment = http2Conn.EncodeHeader(hdrs)
			http2Conn.fr.WriteHeaders(hp)

			actualCodes := []http2.ErrCode{http2.ErrCodeProtocol}
			return TestStreamError(ctx, http2Conn, actualCodes)
		},
	))

	return tg
}

func ConnectionSpecificHeaderFieldsTestGroup() *TestGroup {
	tg := NewTestGroup("8.1.2.2", "Connection-Specific Header Fields")

	tg.AddTestCase(NewTestCase(
		"Sends a HEADERS frame that contains the connection-specific header field",
		"The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.",
		func(ctx *Context) (expected []Result, actual Result) {
			http2Conn := CreateHttp2Conn(ctx, true)
			defer http2Conn.conn.Close()

			hdrs := []hpack.HeaderField{
				pair(":method", "GET"),
				pair(":scheme", "http"),
				pair(":path", "/"),
				pair(":authority", ctx.Authority()),
				pair("connection", "keep-alive"),
			}

			var hp http2.HeadersFrameParam
			hp.StreamID = 1
			hp.EndStream = true
			hp.EndHeaders = true
			hp.BlockFragment = http2Conn.EncodeHeader(hdrs)
			http2Conn.fr.WriteHeaders(hp)

			actualCodes := []http2.ErrCode{http2.ErrCodeProtocol}
			return TestStreamError(ctx, http2Conn, actualCodes)
		},
	))

	tg.AddTestCase(NewTestCase(
		"Sends a HEADERS frame that contains the TE header field that contain any value other than \"trailers\"",
		"The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.",
		func(ctx *Context) (expected []Result, actual Result) {
			http2Conn := CreateHttp2Conn(ctx, true)
			defer http2Conn.conn.Close()

			hdrs := []hpack.HeaderField{
				pair(":method", "GET"),
				pair(":scheme", "http"),
				pair(":path", "/"),
				pair(":authority", ctx.Authority()),
				pair("trailers", "test"),
				pair("te", "trailers, deflate"),
			}

			var hp http2.HeadersFrameParam
			hp.StreamID = 1
			hp.EndStream = true
			hp.EndHeaders = true
			hp.BlockFragment = http2Conn.EncodeHeader(hdrs)
			http2Conn.fr.WriteHeaders(hp)

			actualCodes := []http2.ErrCode{http2.ErrCodeProtocol}
			return TestStreamError(ctx, http2Conn, actualCodes)
		},
	))

	return tg
}

func RequestPseudoHeaderFieldsTestGroup() *TestGroup {
	tg := NewTestGroup("8.1.2.3", "Request Pseudo-Header Fields")

	tg.AddTestCase(NewTestCase(
		"Sends a HEADERS frame that omits mandatory pseudo-header fields",
		"The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.",
		func(ctx *Context) (expected []Result, actual Result) {
			http2Conn := CreateHttp2Conn(ctx, true)
			defer http2Conn.conn.Close()

			hdrs := []hpack.HeaderField{
				pair(":method", "GET"),
				pair(":scheme", "http"),
				pair(":authority", ctx.Authority()),
			}

			var hp http2.HeadersFrameParam
			hp.StreamID = 1
			hp.EndStream = true
			hp.EndHeaders = true
			hp.BlockFragment = http2Conn.EncodeHeader(hdrs)
			http2Conn.fr.WriteHeaders(hp)

			actualCodes := []http2.ErrCode{http2.ErrCodeProtocol}
			return TestStreamError(ctx, http2Conn, actualCodes)
		},
	))

	tg.AddTestCase(NewTestCase(
		"Sends a HEADERS frame containing more than one pseudo-header fields with the same name",
		"The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.",
		func(ctx *Context) (expected []Result, actual Result) {
			http2Conn := CreateHttp2Conn(ctx, true)
			defer http2Conn.conn.Close()

			hdrs := []hpack.HeaderField{
				pair(":method", "GET"),
				pair(":scheme", "http"),
				pair(":path", "http"),
				pair(":authority", ctx.Authority()),
				pair(":method", "GET"),
				pair(":scheme", "http"),
				pair(":path", "http"),
				pair(":authority", ctx.Authority()),
			}

			var hp http2.HeadersFrameParam
			hp.StreamID = 1
			hp.EndStream = true
			hp.EndHeaders = true
			hp.BlockFragment = http2Conn.EncodeHeader(hdrs)
			http2Conn.fr.WriteHeaders(hp)

			actualCodes := []http2.ErrCode{http2.ErrCodeProtocol}
			return TestStreamError(ctx, http2Conn, actualCodes)
		},
	))

	return tg
}

func MalformedRequestsAndResponsesTestGroup() *TestGroup {
	tg := NewTestGroup("8.1.2.6", "Malformed Requests and Responses")

	tg.AddTestCase(NewTestCase(
		"Sends a HEADERS frame that contains the \"content-length\" header field which does not equal the sum of the DATA frame payload lengths",
		"The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.",
		func(ctx *Context) (expected []Result, actual Result) {
			http2Conn := CreateHttp2Conn(ctx, true)
			defer http2Conn.conn.Close()

			hdrs := []hpack.HeaderField{
				pair(":method", "POST"),
				pair(":scheme", "http"),
				pair(":path", "/"),
				pair(":authority", ctx.Authority()),
				pair("content-length", "1"),
			}

			var hp http2.HeadersFrameParam
			hp.StreamID = 1
			hp.EndStream = false
			hp.EndHeaders = true
			hp.BlockFragment = http2Conn.EncodeHeader(hdrs)
			http2Conn.fr.WriteHeaders(hp)
			http2Conn.fr.WriteData(1, true, []byte("test"))

			actualCodes := []http2.ErrCode{http2.ErrCodeProtocol}
			return TestStreamError(ctx, http2Conn, actualCodes)
		},
	))

	tg.AddTestCase(NewTestCase(
		"Sends a HEADERS frame that contains the \"content-length\" header field which does not equal the sum of the multiple DATA frame payload lengths",
		"The endpoint MUST respond with a stream error of type PROTOCOL_ERROR.",
		func(ctx *Context) (expected []Result, actual Result) {
			http2Conn := CreateHttp2Conn(ctx, true)
			defer http2Conn.conn.Close()

			hdrs := []hpack.HeaderField{
				pair(":method", "POST"),
				pair(":scheme", "http"),
				pair(":path", "/"),
				pair(":authority", ctx.Authority()),
				pair("content-length", "1"),
			}

			var hp http2.HeadersFrameParam
			hp.StreamID = 1
			hp.EndStream = false
			hp.EndHeaders = true
			hp.BlockFragment = http2Conn.EncodeHeader(hdrs)
			http2Conn.fr.WriteHeaders(hp)
			http2Conn.fr.WriteData(1, false, []byte("test"))
			http2Conn.fr.WriteData(1, true, []byte("test"))

			actualCodes := []http2.ErrCode{http2.ErrCodeProtocol}
			return TestStreamError(ctx, http2Conn, actualCodes)
		},
	))

	return tg
}
<|endoftext|>


<commit_before>// Copyright (c) 2013-2014 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package btcchain_test

import (
	"strconv"
	"testing"
	"time"

	"github.com/conformal/btcchain"
)

// TestMedianTime tests the medianTime implementation.
func TestMedianTime(t *testing.T) {
	tests := []struct {
		in         []int64
		wantOffset int64
		useDupID   bool
	}{
		// Not enough samples must result in an offset of 0.
		{in: []int64{1}, wantOffset: 0},
		{in: []int64{1, 2}, wantOffset: 0},
		{in: []int64{1, 2, 3}, wantOffset: 0},
		{in: []int64{1, 2, 3, 4}, wantOffset: 0},

		// Various number of entries.  The expected offset is only
		// updated on odd number of elements.
		{in: []int64{-13, 57, -4, -23, -12}, wantOffset: 12},
		{in: []int64{55, -13, 61, -52, 39, 55}, wantOffset: -39},
		{in: []int64{-62, -58, -30, -62, 51, -30, 15}, wantOffset: 30},
		{in: []int64{29, -47, 39, 54, 42, 41, 8, -33}, wantOffset: -39},
		{in: []int64{37, 54, 9, -21, -56, -36, 5, -11, -39}, wantOffset: 11},
		{in: []int64{57, -28, 25, -39, 9, 63, -16, 19, -60, 25}, wantOffset: -9},
		{in: []int64{-5, -4, -3, -2, -1}, wantOffset: 3, useDupID: true},

		// The offset stops being updated once the max number of entries
		// has been reached.  This is actually a bug from Bitcoin Core,
		// but since the time is ultimately used as a part of the
		// consensus rules, it must be mirrored.
		{in: []int64{-67, 67, -50, 24, 63, 17, 58, -14, 5, -32, -52}, wantOffset: -17},
		{in: []int64{-67, 67, -50, 24, 63, 17, 58, -14, 5, -32, -52, 45}, wantOffset: -17},
		{in: []int64{-67, 67, -50, 24, 63, 17, 58, -14, 5, -32, -52, 45, 4}, wantOffset: -17},

		// Offsets that are too far away from the local time should
		// be ignored.
		{in: []int64{-4201, 4202, -4203, 4204, -4205}, wantOffset: 0},

		// Excerise the condition where the median offset is greater
		// than the max allowed adjustment, but there is at least one
		// sample that is close enough to the current time to avoid
		// triggering a warning about an invalid local clock.
		{in: []int64{4201, 4202, 4203, 4204, -299}, wantOffset: 0},
	}

	// Modify the max number of allowed median time entries for these tests.
	btcchain.TstSetMaxMedianTimeEntries(10)
	defer btcchain.TstSetMaxMedianTimeEntries(200)

	for i, test := range tests {
		filter := btcchain.NewMedianTime()
		for j, offset := range test.in {
			id := strconv.Itoa(j)
			tOffset := time.Now().Add(time.Duration(offset) *
				time.Second)
			filter.AddTimeSample(id, tOffset)

			// Ensure the duplicate IDs are ignored.
			if test.useDupID {
				// Modify the offsets to ensure the final median
				// would be different if the duplicate is added.
				tOffset = tOffset.Add(time.Duration(offset) *
					time.Second)
				filter.AddTimeSample(id, tOffset)
			}
		}

		gotOffset := filter.Offset()
		wantOffset := time.Duration(test.wantOffset) * time.Second
		if gotOffset != wantOffset {
			t.Errorf("Offset #%d: unexpected offset -- got %v, "+
				"want %v", i, gotOffset, wantOffset)
			continue
		}

		adjustedTime := time.Unix(filter.AdjustedTime().Unix(), 0)
		wantTime := time.Now().Add(filter.Offset())
		wantTime = time.Unix(wantTime.Unix(), 0)
		if !adjustedTime.Equal(wantTime) {
			t.Errorf("AdjustedTime #%d: unexpected result -- got %v, "+
				"want %v", i, adjustedTime, wantTime)
			continue
		}
	}
}
<commit_msg>Update new time tests to prevent false positives.<commit_after>// Copyright (c) 2013-2014 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package btcchain_test

import (
	"strconv"
	"testing"
	"time"

	"github.com/conformal/btcchain"
)

// TestMedianTime tests the medianTime implementation.
func TestMedianTime(t *testing.T) {
	tests := []struct {
		in         []int64
		wantOffset int64
		useDupID   bool
	}{
		// Not enough samples must result in an offset of 0.
		{in: []int64{1}, wantOffset: 0},
		{in: []int64{1, 2}, wantOffset: 0},
		{in: []int64{1, 2, 3}, wantOffset: 0},
		{in: []int64{1, 2, 3, 4}, wantOffset: 0},

		// Various number of entries.  The expected offset is only
		// updated on odd number of elements.
		{in: []int64{-13, 57, -4, -23, -12}, wantOffset: 12},
		{in: []int64{55, -13, 61, -52, 39, 55}, wantOffset: -39},
		{in: []int64{-62, -58, -30, -62, 51, -30, 15}, wantOffset: 30},
		{in: []int64{29, -47, 39, 54, 42, 41, 8, -33}, wantOffset: -39},
		{in: []int64{37, 54, 9, -21, -56, -36, 5, -11, -39}, wantOffset: 11},
		{in: []int64{57, -28, 25, -39, 9, 63, -16, 19, -60, 25}, wantOffset: -9},
		{in: []int64{-5, -4, -3, -2, -1}, wantOffset: 3, useDupID: true},

		// The offset stops being updated once the max number of entries
		// has been reached.  This is actually a bug from Bitcoin Core,
		// but since the time is ultimately used as a part of the
		// consensus rules, it must be mirrored.
		{in: []int64{-67, 67, -50, 24, 63, 17, 58, -14, 5, -32, -52}, wantOffset: -17},
		{in: []int64{-67, 67, -50, 24, 63, 17, 58, -14, 5, -32, -52, 45}, wantOffset: -17},
		{in: []int64{-67, 67, -50, 24, 63, 17, 58, -14, 5, -32, -52, 45, 4}, wantOffset: -17},

		// Offsets that are too far away from the local time should
		// be ignored.
		{in: []int64{-4201, 4202, -4203, 4204, -4205}, wantOffset: 0},

		// Excerise the condition where the median offset is greater
		// than the max allowed adjustment, but there is at least one
		// sample that is close enough to the current time to avoid
		// triggering a warning about an invalid local clock.
		{in: []int64{4201, 4202, 4203, 4204, -299}, wantOffset: 0},
	}

	// Modify the max number of allowed median time entries for these tests.
	btcchain.TstSetMaxMedianTimeEntries(10)
	defer btcchain.TstSetMaxMedianTimeEntries(200)

	for i, test := range tests {
		filter := btcchain.NewMedianTime()
		for j, offset := range test.in {
			id := strconv.Itoa(j)
			tOffset := time.Now().Add(time.Duration(offset) *
				time.Second)
			filter.AddTimeSample(id, tOffset)

			// Ensure the duplicate IDs are ignored.
			if test.useDupID {
				// Modify the offsets to ensure the final median
				// would be different if the duplicate is added.
				tOffset = tOffset.Add(time.Duration(offset) *
					time.Second)
				filter.AddTimeSample(id, tOffset)
			}
		}

		gotOffset := filter.Offset()
		wantOffset := time.Duration(test.wantOffset) * time.Second
		if gotOffset != wantOffset {
			t.Errorf("Offset #%d: unexpected offset -- got %v, "+
				"want %v", i, gotOffset, wantOffset)
			continue
		}

		// Since it is possible that the time.Now call in AdjustedTime
		// and the time.Now call here in the tests will be off by one
		// second, allow a fudge factor to compensate.
		adjustedTime := filter.AdjustedTime()
		now := time.Unix(time.Now().Unix(), 0)
		wantTime := now.Add(filter.Offset())
		wantTime2 := now.Add(filter.Offset() - time.Second)
		if !adjustedTime.Equal(wantTime) && !adjustedTime.Equal(wantTime2) {
			t.Errorf("AdjustedTime #%d: unexpected result -- got %v, "+
				"want %v or %v", i, adjustedTime, wantTime,
				wantTime2)
			continue
		}
	}
}
<|endoftext|>


<commit_before>package feature

import (
	"strings"

	"github.com/ovhlabs/izanami-go-client"

	"github.com/ovh/cds/engine/api/cache"
	"github.com/ovh/cds/sdk/log"
)

const (
	// FeatWorkflowAsCode is workflow as code feature id
	FeatWorkflowAsCode = "cds:wasc"

	// FeatEnableTracing is the opencensus tracing feature id
	FeatEnableTracing = "cds:tracing"

	// FeatWNode wnode workflow representation
	FeatWNode = "cds:wnode"

	cacheFeatureKey = "feature:"
)

var izanami *client.Client

// CheckContext represents the context send to Izanami to check if the feature is enabled
type CheckContext struct {
	Key string `json:"key"`
}

// ProjectFeatures represents a project and the feature states
type ProjectFeatures struct {
	Key      string          `json:"key"`
	Features map[string]bool `json:"features"`
}

// List all features
func List() []string {
	return []string{FeatWorkflowAsCode, FeatWNode}
}

// Init initialize Izanami client
func Init(apiURL, clientID, clientSecret string) error {
	izc, err := client.New(apiURL, clientID, clientSecret)
	SetClient(izc)
	return err
}

// SetClient set a client driver for Izanami
func SetClient(c *client.Client) {
	izanami = c
}

// GetFeatures tree for the given project from cache, if not found in cache init from Izanami.
func GetFeatures(store cache.Store, projectKey string) map[string]bool {
	projFeats := ProjectFeatures{}

	if store.Get(cacheFeatureKey+projectKey, &projFeats) {
		// if missing features, invalidate cache and rebuild data from Izanami
		var missingFeature bool
		for _, f := range List() {
			if _, ok := projFeats.Features[f]; !ok {
				missingFeature = true
				break
			}
		}
		if !missingFeature {
			return projFeats.Features
		}
	}

	// get all features from Izanami and store in cache
	projFeats = ProjectFeatures{Key: projectKey, Features: make(map[string]bool)}
	for _, f := range List() {
		projFeats.Features[f] = getStatusFromIzanami(f, projectKey)
	}

	// no expiration delay is set, the cache is cleared by Izanami calls on /feature/clean
	store.Set(cacheFeatureKey+projectKey, projFeats)

	return projFeats.Features
}

// IsEnabled check if feature is enabled for the given project.
func IsEnabled(store cache.Store, featureID string, projectKey string) bool {
	fs := GetFeatures(store, projectKey)

	if v, ok := fs[featureID]; ok {
		return v
	}

	// if features not in cache, it means that it's not a key from listed in List() func
	// try to get a value from Izanami
	return getStatusFromIzanami(featureID, projectKey)
}

func getStatusFromIzanami(featureID string, projectKey string) bool {
	// no feature flipping always return active.
	if izanami == nil || izanami.Feature() == nil {
		return true
	}

	// get from Izanami
	resp, errCheck := izanami.Feature().CheckWithContext(featureID, CheckContext{projectKey})
	if errCheck != nil {
		if !strings.Contains(errCheck.Error(), "404") {
			log.Warning("Feature.IsEnabled > Cannot check feature %s: %s", featureID, errCheck)
			return false
		}
		resp.Active = true
	}

	return resp.Active
}

// Clean the feature cache
func Clean(store cache.Store) {
	keys := cache.Key(cacheFeatureKey, "*")
	store.DeleteAll(keys)
}
<commit_msg>fix(api): cache tracing feature (#3669)<commit_after>package feature

import (
	"strings"

	"github.com/ovhlabs/izanami-go-client"

	"github.com/ovh/cds/engine/api/cache"
	"github.com/ovh/cds/sdk/log"
)

const (
	// FeatWorkflowAsCode is workflow as code feature id
	FeatWorkflowAsCode = "cds:wasc"

	// FeatEnableTracing is the opencensus tracing feature id
	FeatEnableTracing = "cds:tracing"

	// FeatWNode wnode workflow representation
	FeatWNode = "cds:wnode"

	cacheFeatureKey = "feature:"
)

var izanami *client.Client

// CheckContext represents the context send to Izanami to check if the feature is enabled
type CheckContext struct {
	Key string `json:"key"`
}

// ProjectFeatures represents a project and the feature states
type ProjectFeatures struct {
	Key      string          `json:"key"`
	Features map[string]bool `json:"features"`
}

// List all features
func List() []string {
	return []string{FeatWorkflowAsCode, FeatWNode, FeatEnableTracing}
}

// Init initialize Izanami client
func Init(apiURL, clientID, clientSecret string) error {
	izc, err := client.New(apiURL, clientID, clientSecret)
	SetClient(izc)
	return err
}

// SetClient set a client driver for Izanami
func SetClient(c *client.Client) {
	izanami = c
}

// GetFeatures tree for the given project from cache, if not found in cache init from Izanami.
func GetFeatures(store cache.Store, projectKey string) map[string]bool {
	projFeats := ProjectFeatures{}

	if store.Get(cacheFeatureKey+projectKey, &projFeats) {
		// if missing features, invalidate cache and rebuild data from Izanami
		var missingFeature bool
		for _, f := range List() {
			if _, ok := projFeats.Features[f]; !ok {
				missingFeature = true
				break
			}
		}
		if !missingFeature {
			return projFeats.Features
		}
	}

	// get all features from Izanami and store in cache
	projFeats = ProjectFeatures{Key: projectKey, Features: make(map[string]bool)}
	for _, f := range List() {
		projFeats.Features[f] = getStatusFromIzanami(f, projectKey)
	}

	// no expiration delay is set, the cache is cleared by Izanami calls on /feature/clean
	store.Set(cacheFeatureKey+projectKey, projFeats)

	return projFeats.Features
}

// IsEnabled check if feature is enabled for the given project.
func IsEnabled(store cache.Store, featureID string, projectKey string) bool {
	fs := GetFeatures(store, projectKey)

	if v, ok := fs[featureID]; ok {
		return v
	}

	// if features not in cache, it means that it's not a key from listed in List() func
	// try to get a value from Izanami
	return getStatusFromIzanami(featureID, projectKey)
}

func getStatusFromIzanami(featureID string, projectKey string) bool {
	// no feature flipping always return active.
	if izanami == nil || izanami.Feature() == nil {
		return true
	}

	// get from Izanami
	resp, errCheck := izanami.Feature().CheckWithContext(featureID, CheckContext{projectKey})
	if errCheck != nil {
		if !strings.Contains(errCheck.Error(), "404") {
			log.Warning("Feature.IsEnabled > Cannot check feature %s: %s", featureID, errCheck)
			return false
		}
		resp.Active = true
	}

	return resp.Active
}

// Clean the feature cache
func Clean(store cache.Store) {
	keys := cache.Key(cacheFeatureKey, "*")
	store.DeleteAll(keys)
}
<|endoftext|>


<commit_before>// Package mmvdump implements a go port of the C mmvdump utility included in PCP Core
//
// https://github.com/performancecopilot/pcp/blob/master/src/pmdas/mmv/mmvdump.c
//
// It has been written for maximum portability with the C equivalent, without having to use cgo or any other ninja stuff
//
// the main difference is that the reader is separate from the cli with the reading primarily implemented in mmvdump.go while the cli is implemented in cmd/mmvdump
//
// the cli application is completely go gettable and outputs the same things, in mostly the same way as the C cli app, to try it out,
//
// ```
// go get github.com/performancecopilot/speed/mmvdump/cmd/mmvdump
// ```
package mmvdump

import (
	"errors"
	"fmt"
	"math"
	"sync"
	"unsafe"
)

func readHeader(data []byte) (*Header, error) {
	if uint64(len(data)) < HeaderLength {
		return nil, errors.New("file too small to contain a valid Header")
	}

	header := (*Header)(unsafe.Pointer(&data[0]))

	if m := header.Magic[:3]; string(m) != "MMV" {
		return nil, fmt.Errorf("Bad Magic: %v", string(m))
	}

	if header.G1 != header.G2 {
		return nil, fmt.Errorf("Mismatched version numbers, %v and %v", header.G1, header.G2)
	}

	return header, nil
}

func readToc(data []byte, offset uint64) (*Toc, error) {
	if uint64(len(data)) < offset+TocLength {
		return nil, errors.New("Incomplete/Partially Written TOC")
	}

	return (*Toc)(unsafe.Pointer(&data[offset])), nil
}

func readInstance(data []byte, offset uint64) (*Instance, error) {
	if uint64(len(data)) < offset+InstanceLength {
		return nil, errors.New("Incomplete/Partially Written Instance")
	}

	return (*Instance)(unsafe.Pointer(&data[offset])), nil
}

func readInstanceDomain(data []byte, offset uint64) (*InstanceDomain, error) {
	if uint64(len(data)) < offset+InstanceDomainLength {
		return nil, errors.New("Incomplete/Partially Written InstanceDomain")
	}

	return (*InstanceDomain)(unsafe.Pointer(&data[offset])), nil
}

func readMetric(data []byte, offset uint64) (*Metric, error) {
	if uint64(len(data)) < offset+MetricLength {
		return nil, errors.New("Incomplete/Partially Written Metric")
	}

	return (*Metric)(unsafe.Pointer(&data[offset])), nil
}

func readValue(data []byte, offset uint64) (*Value, error) {
	if uint64(len(data)) < offset+ValueLength {
		return nil, errors.New("Incomplete/Partially Written Value")
	}

	return (*Value)(unsafe.Pointer(&data[offset])), nil
}

func readString(data []byte, offset uint64) (*String, error) {
	if uint64(len(data)) < offset+StringLength {
		return nil, errors.New("Incomplete/Partially Written String")
	}

	return (*String)(unsafe.Pointer(&data[offset])), nil
}

func readTocs(data []byte, count int32) ([]*Toc, error) {
	tocs := make([]*Toc, count)

	for i := int32(0); i < count; i++ {
		t, err := readToc(data, HeaderLength+uint64(i)*TocLength)
		if err != nil {
			return nil, err
		}
		tocs[i] = t
	}

	return tocs, nil
}

func readInstances(data []byte, offset uint64, count int32) (map[uint64]*Instance, error) {
	var wg sync.WaitGroup
	wg.Add(int(count))

	instances := make(map[uint64]*Instance)

	var (
		err error
		m   sync.Mutex
	)

	for i := int32(0); i < count; i, offset = i+1, offset+InstanceLength {
		go func(offset uint64) {
			if err == nil {
				instance, ierr := readInstance(data, offset)
				if ierr == nil {
					m.Lock()
					instances[offset] = instance
					m.Unlock()
				} else {
					err = ierr
				}
			}
			wg.Done()
		}(offset)
	}

	wg.Wait()

	if err != nil {
		return nil, err
	}

	return instances, nil
}

func readInstanceDomains(data []byte, offset uint64, count int32) (map[uint64]*InstanceDomain, error) {
	var wg sync.WaitGroup
	wg.Add(int(count))

	indoms := make(map[uint64]*InstanceDomain)

	var (
		err error
		m   sync.Mutex
	)

	for i := int32(0); i < count; i, offset = i+1, offset+InstanceDomainLength {
		go func(offset uint64) {
			if err == nil {
				indom, ierr := readInstanceDomain(data, offset)
				if ierr == nil {
					m.Lock()
					indoms[offset] = indom
					m.Unlock()
				} else {
					err = ierr
				}
			}
			wg.Done()
		}(offset)
	}

	wg.Wait()

	if err != nil {
		return nil, err
	}

	return indoms, nil
}

func readMetrics(data []byte, offset uint64, count int32) (map[uint64]*Metric, error) {
	var wg sync.WaitGroup
	wg.Add(int(count))

	metrics := make(map[uint64]*Metric)

	var (
		err error
		m   sync.Mutex
	)

	for i := int32(0); i < count; i, offset = i+1, offset+MetricLength {
		go func(offset uint64) {
			if err == nil {
				metric, merr := readMetric(data, offset)
				if merr == nil {
					m.Lock()
					metrics[offset] = metric
					m.Unlock()
				} else {
					err = merr
				}
			}
			wg.Done()
		}(offset)
	}

	wg.Wait()

	if err != nil {
		return nil, err
	}

	return metrics, nil
}

func readValues(data []byte, offset uint64, count int32) (map[uint64]*Value, error) {
	var wg sync.WaitGroup
	wg.Add(int(count))

	values := make(map[uint64]*Value)

	var (
		err error
		m   sync.Mutex
	)

	for i := int32(0); i < count; i, offset = i+1, offset+ValueLength {
		go func(offset uint64) {
			if err == nil {
				value, verr := readValue(data, offset)
				if verr == nil {
					m.Lock()
					values[offset] = value
					m.Unlock()
				} else {
					err = verr
				}
			}
			wg.Done()
		}(offset)
	}

	wg.Wait()

	if err != nil {
		return nil, err
	}

	return values, nil
}

func readStrings(data []byte, offset uint64, count int32) (map[uint64]*String, error) {
	var wg sync.WaitGroup
	wg.Add(int(count))

	strings := make(map[uint64]*String)

	var (
		err error
		m   sync.Mutex
	)

	for i := int32(0); i < count; i, offset = i+1, offset+StringLength {
		go func(offset uint64) {
			if err == nil {
				str, serr := readString(data, offset)
				if serr == nil {
					m.Lock()
					strings[offset] = str
					m.Unlock()
				} else {
					err = serr
				}
			}
			wg.Done()
		}(offset)
	}

	wg.Wait()

	if err != nil {
		return nil, err
	}

	return strings, nil
}

// Dump creates a data dump from the passed data
func Dump(data []byte) (
	h *Header,
	tocs []*Toc,
	metrics map[uint64]*Metric,
	values map[uint64]*Value,
	instances map[uint64]*Instance,
	indoms map[uint64]*InstanceDomain,
	strings map[uint64]*String,
	err error,
) {
	h, err = readHeader(data)
	if err != nil {
		return nil, nil, nil, nil, nil, nil, nil, err
	}

	tocs, err = readTocs(data, h.Toc)
	if err != nil {
		return nil, nil, nil, nil, nil, nil, nil, err
	}

	var wg sync.WaitGroup
	wg.Add(len(tocs))

	var ierr, inerr, merr, verr, serr error

	for _, toc := range tocs {
		switch toc.Type {
		case TocInstances:
			go func(offset uint64, count int32) {
				instances, ierr = readInstances(data, offset, count)
				wg.Done()
			}(toc.Offset, toc.Count)
		case TocIndoms:
			go func(offset uint64, count int32) {
				indoms, inerr = readInstanceDomains(data, offset, count)
				wg.Done()
			}(toc.Offset, toc.Count)
		case TocMetrics:
			go func(offset uint64, count int32) {
				metrics, merr = readMetrics(data, offset, count)
				wg.Done()
			}(toc.Offset, toc.Count)
		case TocValues:
			go func(offset uint64, count int32) {
				values, verr = readValues(data, offset, count)
				wg.Done()
			}(toc.Offset, toc.Count)
		case TocStrings:
			go func(offset uint64, count int32) {
				strings, serr = readStrings(data, offset, count)
				wg.Done()
			}(toc.Offset, toc.Count)
		}
	}

	wg.Wait()

	switch {
	case ierr != nil:
		return nil, nil, nil, nil, nil, nil, nil, ierr
	case inerr != nil:
		return nil, nil, nil, nil, nil, nil, nil, inerr
	case merr != nil:
		return nil, nil, nil, nil, nil, nil, nil, merr
	case verr != nil:
		return nil, nil, nil, nil, nil, nil, nil, verr
	case serr != nil:
		return nil, nil, nil, nil, nil, nil, nil, serr
	}

	return
}

// FixedVal will infer a fixed size value from the passed data
func FixedVal(data uint64, t Type) (interface{}, error) {
	switch t {
	case Int32Type:
		return int32(data), nil
	case Uint32Type:
		return uint32(data), nil
	case Int64Type:
		return int64(data), nil
	case Uint64Type:
		return data, nil
	case FloatType:
		return math.Float32frombits(uint32(data)), nil
	case DoubleType:
		return math.Float64frombits(data), nil
	}

	return nil, errors.New("invalid type")
}
<commit_msg>mmvdump: fix gocyclo on Dump<commit_after>// Package mmvdump implements a go port of the C mmvdump utility included in PCP Core
//
// https://github.com/performancecopilot/pcp/blob/master/src/pmdas/mmv/mmvdump.c
//
// It has been written for maximum portability with the C equivalent, without having to use cgo or any other ninja stuff
//
// the main difference is that the reader is separate from the cli with the reading primarily implemented in mmvdump.go while the cli is implemented in cmd/mmvdump
//
// the cli application is completely go gettable and outputs the same things, in mostly the same way as the C cli app, to try it out,
//
// ```
// go get github.com/performancecopilot/speed/mmvdump/cmd/mmvdump
// ```
package mmvdump

import (
	"errors"
	"fmt"
	"math"
	"sync"
	"unsafe"
)

func readHeader(data []byte) (*Header, error) {
	if uint64(len(data)) < HeaderLength {
		return nil, errors.New("file too small to contain a valid Header")
	}

	header := (*Header)(unsafe.Pointer(&data[0]))

	if m := header.Magic[:3]; string(m) != "MMV" {
		return nil, fmt.Errorf("Bad Magic: %v", string(m))
	}

	if header.G1 != header.G2 {
		return nil, fmt.Errorf("Mismatched version numbers, %v and %v", header.G1, header.G2)
	}

	return header, nil
}

func readToc(data []byte, offset uint64) (*Toc, error) {
	if uint64(len(data)) < offset+TocLength {
		return nil, errors.New("Incomplete/Partially Written TOC")
	}

	return (*Toc)(unsafe.Pointer(&data[offset])), nil
}

func readInstance(data []byte, offset uint64) (*Instance, error) {
	if uint64(len(data)) < offset+InstanceLength {
		return nil, errors.New("Incomplete/Partially Written Instance")
	}

	return (*Instance)(unsafe.Pointer(&data[offset])), nil
}

func readInstanceDomain(data []byte, offset uint64) (*InstanceDomain, error) {
	if uint64(len(data)) < offset+InstanceDomainLength {
		return nil, errors.New("Incomplete/Partially Written InstanceDomain")
	}

	return (*InstanceDomain)(unsafe.Pointer(&data[offset])), nil
}

func readMetric(data []byte, offset uint64) (*Metric, error) {
	if uint64(len(data)) < offset+MetricLength {
		return nil, errors.New("Incomplete/Partially Written Metric")
	}

	return (*Metric)(unsafe.Pointer(&data[offset])), nil
}

func readValue(data []byte, offset uint64) (*Value, error) {
	if uint64(len(data)) < offset+ValueLength {
		return nil, errors.New("Incomplete/Partially Written Value")
	}

	return (*Value)(unsafe.Pointer(&data[offset])), nil
}

func readString(data []byte, offset uint64) (*String, error) {
	if uint64(len(data)) < offset+StringLength {
		return nil, errors.New("Incomplete/Partially Written String")
	}

	return (*String)(unsafe.Pointer(&data[offset])), nil
}

func readTocs(data []byte, count int32) ([]*Toc, error) {
	tocs := make([]*Toc, count)

	for i := int32(0); i < count; i++ {
		t, err := readToc(data, HeaderLength+uint64(i)*TocLength)
		if err != nil {
			return nil, err
		}
		tocs[i] = t
	}

	return tocs, nil
}

func readInstances(data []byte, offset uint64, count int32) (map[uint64]*Instance, error) {
	var wg sync.WaitGroup
	wg.Add(int(count))

	instances := make(map[uint64]*Instance)

	var (
		err error
		m   sync.Mutex
	)

	for i := int32(0); i < count; i, offset = i+1, offset+InstanceLength {
		go func(offset uint64) {
			if err == nil {
				instance, ierr := readInstance(data, offset)
				if ierr == nil {
					m.Lock()
					instances[offset] = instance
					m.Unlock()
				} else {
					err = ierr
				}
			}
			wg.Done()
		}(offset)
	}

	wg.Wait()

	if err != nil {
		return nil, err
	}

	return instances, nil
}

func readInstanceDomains(data []byte, offset uint64, count int32) (map[uint64]*InstanceDomain, error) {
	var wg sync.WaitGroup
	wg.Add(int(count))

	indoms := make(map[uint64]*InstanceDomain)

	var (
		err error
		m   sync.Mutex
	)

	for i := int32(0); i < count; i, offset = i+1, offset+InstanceDomainLength {
		go func(offset uint64) {
			if err == nil {
				indom, ierr := readInstanceDomain(data, offset)
				if ierr == nil {
					m.Lock()
					indoms[offset] = indom
					m.Unlock()
				} else {
					err = ierr
				}
			}
			wg.Done()
		}(offset)
	}

	wg.Wait()

	if err != nil {
		return nil, err
	}

	return indoms, nil
}

func readMetrics(data []byte, offset uint64, count int32) (map[uint64]*Metric, error) {
	var wg sync.WaitGroup
	wg.Add(int(count))

	metrics := make(map[uint64]*Metric)

	var (
		err error
		m   sync.Mutex
	)

	for i := int32(0); i < count; i, offset = i+1, offset+MetricLength {
		go func(offset uint64) {
			if err == nil {
				metric, merr := readMetric(data, offset)
				if merr == nil {
					m.Lock()
					metrics[offset] = metric
					m.Unlock()
				} else {
					err = merr
				}
			}
			wg.Done()
		}(offset)
	}

	wg.Wait()

	if err != nil {
		return nil, err
	}

	return metrics, nil
}

func readValues(data []byte, offset uint64, count int32) (map[uint64]*Value, error) {
	var wg sync.WaitGroup
	wg.Add(int(count))

	values := make(map[uint64]*Value)

	var (
		err error
		m   sync.Mutex
	)

	for i := int32(0); i < count; i, offset = i+1, offset+ValueLength {
		go func(offset uint64) {
			if err == nil {
				value, verr := readValue(data, offset)
				if verr == nil {
					m.Lock()
					values[offset] = value
					m.Unlock()
				} else {
					err = verr
				}
			}
			wg.Done()
		}(offset)
	}

	wg.Wait()

	if err != nil {
		return nil, err
	}

	return values, nil
}

func readStrings(data []byte, offset uint64, count int32) (map[uint64]*String, error) {
	var wg sync.WaitGroup
	wg.Add(int(count))

	strings := make(map[uint64]*String)

	var (
		err error
		m   sync.Mutex
	)

	for i := int32(0); i < count; i, offset = i+1, offset+StringLength {
		go func(offset uint64) {
			if err == nil {
				str, serr := readString(data, offset)
				if serr == nil {
					m.Lock()
					strings[offset] = str
					m.Unlock()
				} else {
					err = serr
				}
			}
			wg.Done()
		}(offset)
	}

	wg.Wait()

	if err != nil {
		return nil, err
	}

	return strings, nil
}

func readComponents(data []byte, tocs []*Toc) (
	metrics map[uint64]*Metric,
	values map[uint64]*Value,
	instances map[uint64]*Instance,
	indoms map[uint64]*InstanceDomain,
	strings map[uint64]*String,
	ierr, inerr, merr, verr, serr error,
) {
	var wg sync.WaitGroup
	wg.Add(len(tocs))

	for _, toc := range tocs {
		switch toc.Type {
		case TocInstances:
			go func(offset uint64, count int32) {
				instances, ierr = readInstances(data, offset, count)
				wg.Done()
			}(toc.Offset, toc.Count)
		case TocIndoms:
			go func(offset uint64, count int32) {
				indoms, inerr = readInstanceDomains(data, offset, count)
				wg.Done()
			}(toc.Offset, toc.Count)
		case TocMetrics:
			go func(offset uint64, count int32) {
				metrics, merr = readMetrics(data, offset, count)
				wg.Done()
			}(toc.Offset, toc.Count)
		case TocValues:
			go func(offset uint64, count int32) {
				values, verr = readValues(data, offset, count)
				wg.Done()
			}(toc.Offset, toc.Count)
		case TocStrings:
			go func(offset uint64, count int32) {
				strings, serr = readStrings(data, offset, count)
				wg.Done()
			}(toc.Offset, toc.Count)
		}
	}

	wg.Wait()

	return
}

// Dump creates a data dump from the passed data
func Dump(data []byte) (
	h *Header,
	tocs []*Toc,
	metrics map[uint64]*Metric,
	values map[uint64]*Value,
	instances map[uint64]*Instance,
	indoms map[uint64]*InstanceDomain,
	strings map[uint64]*String,
	err error,
) {
	h, err = readHeader(data)
	if err != nil {
		return nil, nil, nil, nil, nil, nil, nil, err
	}

	tocs, err = readTocs(data, h.Toc)
	if err != nil {
		return nil, nil, nil, nil, nil, nil, nil, err
	}

	var ierr, inerr, merr, verr, serr error

	metrics, values, instances, indoms, strings, ierr, inerr, merr, verr, serr = readComponents(data, tocs)

	switch {
	case ierr != nil:
		return nil, nil, nil, nil, nil, nil, nil, ierr
	case inerr != nil:
		return nil, nil, nil, nil, nil, nil, nil, inerr
	case merr != nil:
		return nil, nil, nil, nil, nil, nil, nil, merr
	case verr != nil:
		return nil, nil, nil, nil, nil, nil, nil, verr
	case serr != nil:
		return nil, nil, nil, nil, nil, nil, nil, serr
	}

	return
}

// FixedVal will infer a fixed size value from the passed data
func FixedVal(data uint64, t Type) (interface{}, error) {
	switch t {
	case Int32Type:
		return int32(data), nil
	case Uint32Type:
		return uint32(data), nil
	case Int64Type:
		return int64(data), nil
	case Uint64Type:
		return data, nil
	case FloatType:
		return math.Float32frombits(uint32(data)), nil
	case DoubleType:
		return math.Float64frombits(data), nil
	}

	return nil, errors.New("invalid type")
}
<|endoftext|>