<commit_before>// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package zap

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"math"
	"strconv"
	"sync"
	"time"
	"unicode/utf8"
)

const (
	// For JSON-escaping; see jsonEncoder.safeAddString below.
	_hex = "0123456789abcdef"
	// Initial buffer size for encoders.
	_initialBufSize = 1024
)

var (
	// errNilSink signals that Encoder.WriteEntry was called with a nil WriteSyncer.
	errNilSink = errors.New("can't write encoded message a nil WriteSyncer")

	// Default formatters for JSON encoders.
	defaultMessageF = MessageKey("msg")
	defaultTimeF    = EpochFormatter("ts")
	defaultLevelF   = LevelString("level")

	jsonPool = sync.Pool{New: func() interface{} {
		return &jsonEncoder{
			// Pre-allocate a reasonably-sized buffer for each encoder.
			bytes: make([]byte, 0, _initialBufSize),
		}
	}}
)

// jsonEncoder is an Encoder implementation that writes JSON.
type jsonEncoder struct {
	bytes    []byte
	messageF MessageFormatter
	timeF    TimeFormatter
	levelF   LevelFormatter
}

// NewJSONEncoder creates a fast, low-allocation JSON encoder. By default, JSON
// encoders put the log message under the "msg" key, the timestamp (as
// floating-point seconds since epoch) under the "ts" key, and the log level
// under the "level" key. The encoder appropriately escapes all field keys and
// values.
//
// Note that the encoder doesn't deduplicate keys, so it's possible to produce a
// message like
//   {"foo":"bar","foo":"baz"}
// This is permitted by the JSON specification, but not encouraged. Many
// libraries will ignore duplicate key-value pairs (typically keeping the last
// pair) when unmarshaling, but users should attempt to avoid adding duplicate
// keys.
func NewJSONEncoder(options ...JSONOption) Encoder {
	enc := jsonPool.Get().(*jsonEncoder)
	enc.truncate()

	enc.messageF = defaultMessageF
	enc.timeF = defaultTimeF
	enc.levelF = defaultLevelF
	for _, opt := range options {
		opt.apply(enc)
	}

	return enc
}

func (enc *jsonEncoder) Free() {
	jsonPool.Put(enc)
}

// AddString adds a string key and value to the encoder's fields. Both key and
// value are JSON-escaped.
func (enc *jsonEncoder) AddString(key, val string) {
	enc.addKey(key)
	enc.bytes = append(enc.bytes, '"')
	enc.safeAddString(val)
	enc.bytes = append(enc.bytes, '"')
}

// AddBool adds a string key and a boolean value to the encoder's fields. The
// key is JSON-escaped.
func (enc *jsonEncoder) AddBool(key string, val bool) {
	enc.addKey(key)
	if val {
		enc.bytes = append(enc.bytes, "true"...)
		return
	}
	enc.bytes = append(enc.bytes, "false"...)
}

// AddInt adds a string key and integer value to the encoder's fields. The key
// is JSON-escaped.
func (enc *jsonEncoder) AddInt(key string, val int) {
	enc.AddInt64(key, int64(val))
}

// AddInt64 adds a string key and int64 value to the encoder's fields. The key
// is JSON-escaped.
func (enc *jsonEncoder) AddInt64(key string, val int64) {
	enc.addKey(key)
	enc.bytes = strconv.AppendInt(enc.bytes, val, 10)
}

// AddUint adds a string key and integer value to the encoder's fields. The key
// is JSON-escaped.
func (enc *jsonEncoder) AddUint(key string, val uint) {
	enc.AddUint64(key, uint64(val))
}

// AddUint64 adds a string key and integer value to the encoder's fields. The key
// is JSON-escaped.
func (enc *jsonEncoder) AddUint64(key string, val uint64) {
	enc.addKey(key)
	enc.bytes = strconv.AppendUint(enc.bytes, val, 10)
}

// AddFloat64 adds a string key and float64 value to the encoder's fields. The
// key is JSON-escaped, and the floating-point value is encoded using
// strconv.FormatFloat's 'f' option (always use grade-school notation, even for
// large exponents).
func (enc *jsonEncoder) AddFloat64(key string, val float64) {
	enc.addKey(key)
	switch {
	case math.IsNaN(val):
		enc.bytes = append(enc.bytes, `"NaN"`...)
	case math.IsInf(val, 1):
		enc.bytes = append(enc.bytes, `"+Inf"`...)
	case math.IsInf(val, -1):
		enc.bytes = append(enc.bytes, `"-Inf"`...)
	default:
		enc.bytes = strconv.AppendFloat(enc.bytes, val, 'f', -1, 64)
	}
}

// AddMarshaler adds a LogMarshaler to the encoder's fields.
func (enc *jsonEncoder) AddMarshaler(key string, obj LogMarshaler) error {
	enc.addKey(key)
	enc.bytes = append(enc.bytes, '{')
	err := obj.MarshalLog(enc)
	enc.bytes = append(enc.bytes, '}')
	return err
}

// AddObject uses reflection to add an arbitrary object to the logging context.
func (enc *jsonEncoder) AddObject(key string, obj interface{}) error {
	marshaled, err := json.Marshal(obj)
	if err != nil {
		return err
	}
	enc.addKey(key)
	enc.bytes = append(enc.bytes, marshaled...)
	return nil
}

// Clone copies the current encoder, including any data already encoded.
func (enc *jsonEncoder) Clone() Encoder {
	clone := jsonPool.Get().(*jsonEncoder)
	clone.truncate()
	clone.bytes = append(clone.bytes, enc.bytes...)
	clone.messageF = enc.messageF
	clone.timeF = enc.timeF
	clone.levelF = enc.levelF
	return clone
}

// WriteEntry writes a complete log message to the supplied writer, including
// the encoder's accumulated fields. It doesn't modify or lock the encoder's
// underlying byte slice. It's safe to call from multiple goroutines, but it's
// not safe to call WriteEntry while adding fields.
func (enc *jsonEncoder) WriteEntry(sink io.Writer, msg string, lvl Level, t time.Time) error {
	if sink == nil {
		return errNilSink
	}

	final := jsonPool.Get().(*jsonEncoder)
	final.truncate()
	final.bytes = append(final.bytes, '{')
	enc.levelF(lvl).AddTo(final)
	enc.timeF(t).AddTo(final)
	enc.messageF(msg).AddTo(final)
	if len(enc.bytes) > 0 {
		if len(final.bytes) > 1 {
			// All the formatters may have been no-ops.
			final.bytes = append(final.bytes, ',')
		}
		final.bytes = append(final.bytes, enc.bytes...)
	}
	final.bytes = append(final.bytes, '}', '\n')

	expectedBytes := len(final.bytes)
	n, err := sink.Write(final.bytes)
	final.Free()
	if err != nil {
		return err
	}
	if n != expectedBytes {
		return fmt.Errorf("incomplete write: only wrote %v of %v bytes", n, expectedBytes)
	}
	return nil
}

func (enc *jsonEncoder) truncate() {
	enc.bytes = enc.bytes[:0]
}

func (enc *jsonEncoder) addKey(key string) {
	last := len(enc.bytes) - 1
	// At some point, we'll also want to support arrays.
	if last >= 0 && enc.bytes[last] != '{' {
		enc.bytes = append(enc.bytes, ',')
	}
	enc.bytes = append(enc.bytes, '"')
	enc.safeAddString(key)
	enc.bytes = append(enc.bytes, '"', ':')
}

// safeAddString JSON-escapes a string and appends it to the internal buffer.
// Unlike the standard library's escaping function, it doesn't attempt to
// protect the user from browser vulnerabilities or JSONP-related problems.
func (enc *jsonEncoder) safeAddString(s string) {
	for i := 0; i < len(s); {
		if b := s[i]; b < utf8.RuneSelf {
			i++
			if 0x20 <= b && b != '\\' && b != '"' {
				enc.bytes = append(enc.bytes, b)
				continue
			}
			switch b {
			case '\\', '"':
				enc.bytes = append(enc.bytes, '\\', b)
			case '\n':
				enc.bytes = append(enc.bytes, '\\', 'n')
			case '\r':
				enc.bytes = append(enc.bytes, '\\', 'r')
			case '\t':
				enc.bytes = append(enc.bytes, '\\', 't')
			default:
				// Encode bytes < 0x20, except for the escape sequences above.
				enc.bytes = append(enc.bytes, `\u00`...)
				enc.bytes = append(enc.bytes, _hex[b>>4], _hex[b&0xF])
			}
			continue
		}
		c, size := utf8.DecodeRuneInString(s[i:])
		if c == utf8.RuneError && size == 1 {
			enc.bytes = append(enc.bytes, `\ufffd`...)
			i++
			continue
		}
		enc.bytes = append(enc.bytes, s[i:i+size]...)
		i += size
	}
}
<commit_msg>Use strconv.AppendBool for adding bools (#148)<commit_after>// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package zap

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"math"
	"strconv"
	"sync"
	"time"
	"unicode/utf8"
)

const (
	// For JSON-escaping; see jsonEncoder.safeAddString below.
	_hex = "0123456789abcdef"
	// Initial buffer size for encoders.
	_initialBufSize = 1024
)

var (
	// errNilSink signals that Encoder.WriteEntry was called with a nil WriteSyncer.
	errNilSink = errors.New("can't write encoded message a nil WriteSyncer")

	// Default formatters for JSON encoders.
	defaultMessageF = MessageKey("msg")
	defaultTimeF    = EpochFormatter("ts")
	defaultLevelF   = LevelString("level")

	jsonPool = sync.Pool{New: func() interface{} {
		return &jsonEncoder{
			// Pre-allocate a reasonably-sized buffer for each encoder.
			bytes: make([]byte, 0, _initialBufSize),
		}
	}}
)

// jsonEncoder is an Encoder implementation that writes JSON.
type jsonEncoder struct {
	bytes    []byte
	messageF MessageFormatter
	timeF    TimeFormatter
	levelF   LevelFormatter
}

// NewJSONEncoder creates a fast, low-allocation JSON encoder. By default, JSON
// encoders put the log message under the "msg" key, the timestamp (as
// floating-point seconds since epoch) under the "ts" key, and the log level
// under the "level" key. The encoder appropriately escapes all field keys and
// values.
//
// Note that the encoder doesn't deduplicate keys, so it's possible to produce a
// message like
//   {"foo":"bar","foo":"baz"}
// This is permitted by the JSON specification, but not encouraged. Many
// libraries will ignore duplicate key-value pairs (typically keeping the last
// pair) when unmarshaling, but users should attempt to avoid adding duplicate
// keys.
func NewJSONEncoder(options ...JSONOption) Encoder {
	enc := jsonPool.Get().(*jsonEncoder)
	enc.truncate()

	enc.messageF = defaultMessageF
	enc.timeF = defaultTimeF
	enc.levelF = defaultLevelF
	for _, opt := range options {
		opt.apply(enc)
	}

	return enc
}

func (enc *jsonEncoder) Free() {
	jsonPool.Put(enc)
}

// AddString adds a string key and value to the encoder's fields. Both key and
// value are JSON-escaped.
func (enc *jsonEncoder) AddString(key, val string) {
	enc.addKey(key)
	enc.bytes = append(enc.bytes, '"')
	enc.safeAddString(val)
	enc.bytes = append(enc.bytes, '"')
}

// AddBool adds a string key and a boolean value to the encoder's fields. The
// key is JSON-escaped.
func (enc *jsonEncoder) AddBool(key string, val bool) {
	enc.addKey(key)
	enc.bytes = strconv.AppendBool(enc.bytes, val)
}

// AddInt adds a string key and integer value to the encoder's fields. The key
// is JSON-escaped.
func (enc *jsonEncoder) AddInt(key string, val int) {
	enc.AddInt64(key, int64(val))
}

// AddInt64 adds a string key and int64 value to the encoder's fields. The key
// is JSON-escaped.
func (enc *jsonEncoder) AddInt64(key string, val int64) {
	enc.addKey(key)
	enc.bytes = strconv.AppendInt(enc.bytes, val, 10)
}

// AddUint adds a string key and integer value to the encoder's fields. The key
// is JSON-escaped.
func (enc *jsonEncoder) AddUint(key string, val uint) {
	enc.AddUint64(key, uint64(val))
}

// AddUint64 adds a string key and integer value to the encoder's fields. The key
// is JSON-escaped.
func (enc *jsonEncoder) AddUint64(key string, val uint64) {
	enc.addKey(key)
	enc.bytes = strconv.AppendUint(enc.bytes, val, 10)
}

// AddFloat64 adds a string key and float64 value to the encoder's fields. The
// key is JSON-escaped, and the floating-point value is encoded using
// strconv.FormatFloat's 'f' option (always use grade-school notation, even for
// large exponents).
func (enc *jsonEncoder) AddFloat64(key string, val float64) {
	enc.addKey(key)
	switch {
	case math.IsNaN(val):
		enc.bytes = append(enc.bytes, `"NaN"`...)
	case math.IsInf(val, 1):
		enc.bytes = append(enc.bytes, `"+Inf"`...)
	case math.IsInf(val, -1):
		enc.bytes = append(enc.bytes, `"-Inf"`...)
	default:
		enc.bytes = strconv.AppendFloat(enc.bytes, val, 'f', -1, 64)
	}
}

// AddMarshaler adds a LogMarshaler to the encoder's fields.
func (enc *jsonEncoder) AddMarshaler(key string, obj LogMarshaler) error {
	enc.addKey(key)
	enc.bytes = append(enc.bytes, '{')
	err := obj.MarshalLog(enc)
	enc.bytes = append(enc.bytes, '}')
	return err
}

// AddObject uses reflection to add an arbitrary object to the logging context.
func (enc *jsonEncoder) AddObject(key string, obj interface{}) error {
	marshaled, err := json.Marshal(obj)
	if err != nil {
		return err
	}
	enc.addKey(key)
	enc.bytes = append(enc.bytes, marshaled...)
	return nil
}

// Clone copies the current encoder, including any data already encoded.
func (enc *jsonEncoder) Clone() Encoder {
	clone := jsonPool.Get().(*jsonEncoder)
	clone.truncate()
	clone.bytes = append(clone.bytes, enc.bytes...)
	clone.messageF = enc.messageF
	clone.timeF = enc.timeF
	clone.levelF = enc.levelF
	return clone
}

// WriteEntry writes a complete log message to the supplied writer, including
// the encoder's accumulated fields. It doesn't modify or lock the encoder's
// underlying byte slice. It's safe to call from multiple goroutines, but it's
// not safe to call WriteEntry while adding fields.
func (enc *jsonEncoder) WriteEntry(sink io.Writer, msg string, lvl Level, t time.Time) error {
	if sink == nil {
		return errNilSink
	}

	final := jsonPool.Get().(*jsonEncoder)
	final.truncate()
	final.bytes = append(final.bytes, '{')
	enc.levelF(lvl).AddTo(final)
	enc.timeF(t).AddTo(final)
	enc.messageF(msg).AddTo(final)
	if len(enc.bytes) > 0 {
		if len(final.bytes) > 1 {
			// All the formatters may have been no-ops.
			final.bytes = append(final.bytes, ',')
		}
		final.bytes = append(final.bytes, enc.bytes...)
	}
	final.bytes = append(final.bytes, '}', '\n')

	expectedBytes := len(final.bytes)
	n, err := sink.Write(final.bytes)
	final.Free()
	if err != nil {
		return err
	}
	if n != expectedBytes {
		return fmt.Errorf("incomplete write: only wrote %v of %v bytes", n, expectedBytes)
	}
	return nil
}

func (enc *jsonEncoder) truncate() {
	enc.bytes = enc.bytes[:0]
}

func (enc *jsonEncoder) addKey(key string) {
	last := len(enc.bytes) - 1
	// At some point, we'll also want to support arrays.
	if last >= 0 && enc.bytes[last] != '{' {
		enc.bytes = append(enc.bytes, ',')
	}
	enc.bytes = append(enc.bytes, '"')
	enc.safeAddString(key)
	enc.bytes = append(enc.bytes, '"', ':')
}

// safeAddString JSON-escapes a string and appends it to the internal buffer.
// Unlike the standard library's escaping function, it doesn't attempt to
// protect the user from browser vulnerabilities or JSONP-related problems.
func (enc *jsonEncoder) safeAddString(s string) {
	for i := 0; i < len(s); {
		if b := s[i]; b < utf8.RuneSelf {
			i++
			if 0x20 <= b && b != '\\' && b != '"' {
				enc.bytes = append(enc.bytes, b)
				continue
			}
			switch b {
			case '\\', '"':
				enc.bytes = append(enc.bytes, '\\', b)
			case '\n':
				enc.bytes = append(enc.bytes, '\\', 'n')
			case '\r':
				enc.bytes = append(enc.bytes, '\\', 'r')
			case '\t':
				enc.bytes = append(enc.bytes, '\\', 't')
			default:
				// Encode bytes < 0x20, except for the escape sequences above.
				enc.bytes = append(enc.bytes, `\u00`...)
				enc.bytes = append(enc.bytes, _hex[b>>4], _hex[b&0xF])
			}
			continue
		}
		c, size := utf8.DecodeRuneInString(s[i:])
		if c == utf8.RuneError && size == 1 {
			enc.bytes = append(enc.bytes, `\ufffd`...)
			i++
			continue
		}
		enc.bytes = append(enc.bytes, s[i:i+size]...)
		i += size
	}
}
<|endoftext|>


<commit_before>package jsonmap

import "testing"

type InnerThing struct {
	Foo   string
	AnInt int
	ABool bool
}

type OuterThing struct {
	InnerThing InnerThing
}

type OuterPointerThing struct {
	InnerThing *InnerThing
}

type UnregisteredThing struct {
}

var InnerThingTypeMap = TypeMap{
	InnerThing{},
	[]MappedField{
		{
			StructFieldName: "Foo",
			JSONFieldName:   "foo",
			Validator:       String(1, 12),
			Optional:        true,
		},
		{
			StructFieldName: "AnInt",
			JSONFieldName:   "an_int",
			Validator:       Integer(0, 10),
			Optional:        true,
		},
		{
			StructFieldName: "ABool",
			JSONFieldName:   "a_bool",
			Validator:       Boolean(),
			Optional:        true,
		},
	},
}

var OuterThingTypeMap = TypeMap{
	OuterThing{},
	[]MappedField{
		{
			StructFieldName: "InnerThing",
			JSONFieldName:   "inner_thing",
			Contains:        InnerThingTypeMap,
		},
	},
}

var OuterPointerThingTypeMap = TypeMap{
	OuterPointerThing{},
	[]MappedField{
		{
			StructFieldName: "InnerThing",
			JSONFieldName:   "inner_thing",
			Contains:        InnerThingTypeMap,
		},
	},
}

var TestTypeMapper = NewTypeMapper(
	InnerThingTypeMap,
	OuterThingTypeMap,
	OuterPointerThingTypeMap,
)

func TestValidateInnerThing(t *testing.T) {
	v := &InnerThing{}
	err := TestTypeMapper.Unmarshal([]byte(`{"foo": "fooz", "an_int": 10, "a_bool": true}`), v)
	if err != nil {
		t.Fatal(err)
	}
	if v.Foo != "fooz" {
		t.Fatal("Field Foo does not have expected value 'fooz':", v.Foo)
	}
}

func TestValidateOuterThing(t *testing.T) {
	v := &OuterThing{}
	err := TestTypeMapper.Unmarshal([]byte(`{"inner_thing": {"foo": "fooz"}}`), v)
	if err != nil {
		t.Fatal(err)
	}
	if v.InnerThing.Foo != "fooz" {
		t.Fatal("Inner field Foo does not have expected value 'fooz':", v.InnerThing.Foo)
	}
}

func TestValidateUnregisteredThing(t *testing.T) {
	defer func() {
		if r := recover(); r == nil {
			t.Fatal("No panic")
		}
	}()
	v := &UnregisteredThing{}
	TestTypeMapper.Unmarshal([]byte(`{}`), v)
	t.Fatal("Unexpected success")
}

func TestValidateStringTypeMismatch(t *testing.T) {
	v := &InnerThing{}
	err := TestTypeMapper.Unmarshal([]byte(`{"foo": 12.0}`), v)
	if err == nil {
		t.Fatal("Unexpected success")
	}
	if err.Error() != "error validating field 'foo': not a string" {
		t.Fatal("Unexpected error message:", err.Error())
	}
}

func TestValidateStringTooShort(t *testing.T) {
	v := &InnerThing{}
	err := TestTypeMapper.Unmarshal([]byte(`{"foo": ""}`), v)
	if err == nil {
		t.Fatal("Unexpected success")
	}
	if err.Error() != "error validating field 'foo': too short, must be at least 1 characters" {
		t.Fatal("Unexpected error message:", err.Error())
	}
}

func TestValidateStringTooLong(t *testing.T) {
	v := &InnerThing{}
	err := TestTypeMapper.Unmarshal([]byte(`{"foo": "thisvalueistoolong"}`), v)
	if err == nil {
		t.Fatal("Unexpected success")
	}
	if err.Error() != "error validating field 'foo': too long, may not be more than 12 characters" {
		t.Fatal("Unexpected error message:", err.Error())
	}
}

func TestValidateBooleanTypeMismatch(t *testing.T) {
	v := &InnerThing{}
	err := TestTypeMapper.Unmarshal([]byte(`{"a_bool": 12.0}`), v)
	if err == nil {
		t.Fatal("Unexpected success")
	}
	if err.Error() != "error validating field 'a_bool': not a boolean" {
		t.Fatal("Unexpected error message:", err.Error())
	}
}

func TestValidateIntegerTypeMismatch(t *testing.T) {
	v := &InnerThing{}
	err := TestTypeMapper.Unmarshal([]byte(`{"an_int": false}`), v)
	if err == nil {
		t.Fatal("Unexpected success")
	}
	if err.Error() != "error validating field 'an_int': not an integer" {
		t.Fatal("Unexpected error message:", err.Error())
	}
}

func TestValidateIntegerNumericTypeMismatch(t *testing.T) {
	v := &InnerThing{}
	err := TestTypeMapper.Unmarshal([]byte(`{"an_int": 12.1}`), v)
	if err == nil {
		t.Fatal("Unexpected success")
	}
	if err.Error() != "error validating field 'an_int': not an integer" {
		t.Fatal("Unexpected error message:", err.Error())
	}
}

func TestValidateIntegerTooSmall(t *testing.T) {
	v := &InnerThing{}
	err := TestTypeMapper.Unmarshal([]byte(`{"an_int": -1}`), v)
	if err == nil {
		t.Fatal("Unexpected success")
	}
	if err.Error() != "error validating field 'an_int': too small, must be at least 0" {
		t.Fatal("Unexpected error message:", err.Error())
	}
}

func TestValidateIntegerTooLarge(t *testing.T) {
	v := &InnerThing{}
	err := TestTypeMapper.Unmarshal([]byte(`{"an_int": 2048}`), v)
	if err == nil {
		t.Fatal("Unexpected success")
	}
	if err.Error() != "error validating field 'an_int': too large, may not be larger than 10" {
		t.Fatal("Unexpected error message:", err.Error())
	}
}

func TestMarshalInnerThing(t *testing.T) {
	v := &InnerThing{
		Foo:   "bar",
		AnInt: 7,
		ABool: true,
	}
	data, err := TestTypeMapper.Marshal(v)
	if err != nil {
		t.Fatal(err)
	}
	if string(data) != `{"a_bool":true,"an_int":7,"foo":"bar"}` {
		t.Fatal("Unexpected Marshal output:", string(data))
	}
}

func TestMarshalOuterThing(t *testing.T) {
	v := &OuterThing{
		InnerThing: InnerThing{
			Foo:   "bar",
			AnInt: 3,
			ABool: false,
		},
	}
	data, err := TestTypeMapper.Marshal(v)
	if err != nil {
		t.Fatal(err)
	}
	if string(data) != `{"inner_thing":{"a_bool":false,"an_int":3,"foo":"bar"}}` {
		t.Fatal("Unexpected Marshal output:", string(data))
	}
}

func TestMarshalOuterPointerThing(t *testing.T) {
	v := &OuterPointerThing{
		InnerThing: &InnerThing{
			Foo:   "bar",
			AnInt: 3,
			ABool: false,
		},
	}
	data, err := TestTypeMapper.Marshal(v)
	if err != nil {
		t.Fatal(err)
	}
	if string(data) != `{"inner_thing":{"a_bool":false,"an_int":3,"foo":"bar"}}` {
		t.Fatal("Unexpected Marshal output:", string(data))
	}
}
<commit_msg>Add tests of marshaling and unmarshaling with an invalid struct field name<commit_after>package jsonmap

import "testing"

type InnerThing struct {
	Foo   string
	AnInt int
	ABool bool
}

type OuterThing struct {
	InnerThing InnerThing
}

type OuterPointerThing struct {
	InnerThing *InnerThing
}

type UnregisteredThing struct {
}

type TypoedThing struct {
	Correct bool
}

var InnerThingTypeMap = TypeMap{
	InnerThing{},
	[]MappedField{
		{
			StructFieldName: "Foo",
			JSONFieldName:   "foo",
			Validator:       String(1, 12),
			Optional:        true,
		},
		{
			StructFieldName: "AnInt",
			JSONFieldName:   "an_int",
			Validator:       Integer(0, 10),
			Optional:        true,
		},
		{
			StructFieldName: "ABool",
			JSONFieldName:   "a_bool",
			Validator:       Boolean(),
			Optional:        true,
		},
	},
}

var OuterThingTypeMap = TypeMap{
	OuterThing{},
	[]MappedField{
		{
			StructFieldName: "InnerThing",
			JSONFieldName:   "inner_thing",
			Contains:        InnerThingTypeMap,
		},
	},
}

var OuterPointerThingTypeMap = TypeMap{
	OuterPointerThing{},
	[]MappedField{
		{
			StructFieldName: "InnerThing",
			JSONFieldName:   "inner_thing",
			Contains:        InnerThingTypeMap,
		},
	},
}

var TypoedThingTypeMap = TypeMap{
	TypoedThing{},
	[]MappedField{
		{
			StructFieldName: "Incorrect",
			JSONFieldName:   "correct",
			Validator:       Boolean(),
		},
	},
}

var TestTypeMapper = NewTypeMapper(
	InnerThingTypeMap,
	OuterThingTypeMap,
	OuterPointerThingTypeMap,
	TypoedThingTypeMap,
)

func TestValidateInnerThing(t *testing.T) {
	v := &InnerThing{}
	err := TestTypeMapper.Unmarshal([]byte(`{"foo": "fooz", "an_int": 10, "a_bool": true}`), v)
	if err != nil {
		t.Fatal(err)
	}
	if v.Foo != "fooz" {
		t.Fatal("Field Foo does not have expected value 'fooz':", v.Foo)
	}
}

func TestValidateOuterThing(t *testing.T) {
	v := &OuterThing{}
	err := TestTypeMapper.Unmarshal([]byte(`{"inner_thing": {"foo": "fooz"}}`), v)
	if err != nil {
		t.Fatal(err)
	}
	if v.InnerThing.Foo != "fooz" {
		t.Fatal("Inner field Foo does not have expected value 'fooz':", v.InnerThing.Foo)
	}
}

func TestValidateUnregisteredThing(t *testing.T) {
	defer func() {
		if r := recover(); r == nil {
			t.Fatal("No panic")
		}
	}()
	v := &UnregisteredThing{}
	TestTypeMapper.Unmarshal([]byte(`{}`), v)
	t.Fatal("Unexpected success")
}

func TestValidateStringTypeMismatch(t *testing.T) {
	v := &InnerThing{}
	err := TestTypeMapper.Unmarshal([]byte(`{"foo": 12.0}`), v)
	if err == nil {
		t.Fatal("Unexpected success")
	}
	if err.Error() != "error validating field 'foo': not a string" {
		t.Fatal("Unexpected error message:", err.Error())
	}
}

func TestValidateStringTooShort(t *testing.T) {
	v := &InnerThing{}
	err := TestTypeMapper.Unmarshal([]byte(`{"foo": ""}`), v)
	if err == nil {
		t.Fatal("Unexpected success")
	}
	if err.Error() != "error validating field 'foo': too short, must be at least 1 characters" {
		t.Fatal("Unexpected error message:", err.Error())
	}
}

func TestValidateStringTooLong(t *testing.T) {
	v := &InnerThing{}
	err := TestTypeMapper.Unmarshal([]byte(`{"foo": "thisvalueistoolong"}`), v)
	if err == nil {
		t.Fatal("Unexpected success")
	}
	if err.Error() != "error validating field 'foo': too long, may not be more than 12 characters" {
		t.Fatal("Unexpected error message:", err.Error())
	}
}

func TestValidateBooleanTypeMismatch(t *testing.T) {
	v := &InnerThing{}
	err := TestTypeMapper.Unmarshal([]byte(`{"a_bool": 12.0}`), v)
	if err == nil {
		t.Fatal("Unexpected success")
	}
	if err.Error() != "error validating field 'a_bool': not a boolean" {
		t.Fatal("Unexpected error message:", err.Error())
	}
}

func TestValidateIntegerTypeMismatch(t *testing.T) {
	v := &InnerThing{}
	err := TestTypeMapper.Unmarshal([]byte(`{"an_int": false}`), v)
	if err == nil {
		t.Fatal("Unexpected success")
	}
	if err.Error() != "error validating field 'an_int': not an integer" {
		t.Fatal("Unexpected error message:", err.Error())
	}
}

func TestValidateIntegerNumericTypeMismatch(t *testing.T) {
	v := &InnerThing{}
	err := TestTypeMapper.Unmarshal([]byte(`{"an_int": 12.1}`), v)
	if err == nil {
		t.Fatal("Unexpected success")
	}
	if err.Error() != "error validating field 'an_int': not an integer" {
		t.Fatal("Unexpected error message:", err.Error())
	}
}

func TestValidateIntegerTooSmall(t *testing.T) {
	v := &InnerThing{}
	err := TestTypeMapper.Unmarshal([]byte(`{"an_int": -1}`), v)
	if err == nil {
		t.Fatal("Unexpected success")
	}
	if err.Error() != "error validating field 'an_int': too small, must be at least 0" {
		t.Fatal("Unexpected error message:", err.Error())
	}
}

func TestValidateIntegerTooLarge(t *testing.T) {
	v := &InnerThing{}
	err := TestTypeMapper.Unmarshal([]byte(`{"an_int": 2048}`), v)
	if err == nil {
		t.Fatal("Unexpected success")
	}
	if err.Error() != "error validating field 'an_int': too large, may not be larger than 10" {
		t.Fatal("Unexpected error message:", err.Error())
	}
}

func TestMarshalInnerThing(t *testing.T) {
	v := &InnerThing{
		Foo:   "bar",
		AnInt: 7,
		ABool: true,
	}
	data, err := TestTypeMapper.Marshal(v)
	if err != nil {
		t.Fatal(err)
	}
	if string(data) != `{"a_bool":true,"an_int":7,"foo":"bar"}` {
		t.Fatal("Unexpected Marshal output:", string(data))
	}
}

func TestMarshalOuterThing(t *testing.T) {
	v := &OuterThing{
		InnerThing: InnerThing{
			Foo:   "bar",
			AnInt: 3,
			ABool: false,
		},
	}
	data, err := TestTypeMapper.Marshal(v)
	if err != nil {
		t.Fatal(err)
	}
	if string(data) != `{"inner_thing":{"a_bool":false,"an_int":3,"foo":"bar"}}` {
		t.Fatal("Unexpected Marshal output:", string(data))
	}
}

func TestMarshalOuterPointerThing(t *testing.T) {
	v := &OuterPointerThing{
		InnerThing: &InnerThing{
			Foo:   "bar",
			AnInt: 3,
			ABool: false,
		},
	}
	data, err := TestTypeMapper.Marshal(v)
	if err != nil {
		t.Fatal(err)
	}
	if string(data) != `{"inner_thing":{"a_bool":false,"an_int":3,"foo":"bar"}}` {
		t.Fatal("Unexpected Marshal output:", string(data))
	}
}

func TestMarshalNoSuchStructField(t *testing.T) {
	defer func() {
		r := recover()
		if r == nil {
			t.Fatal("No panic")
		}
		if r != "No such underlying field: Incorrect" {
			t.Fatal("Incorrect panic message", r)
		}
	}()
	v := &TypoedThing{
		Correct: false,
	}
	TestTypeMapper.Marshal(v)
}

func TestUnmarshalNoSuchStructField(t *testing.T) {
	defer func() {
		r := recover()
		if r == nil {
			t.Fatal("No panic")
		}
		if r != "No such underlying field: Incorrect" {
			t.Fatal("Incorrect panic message", r)
		}
	}()
	v := &TypoedThing{}
	TestTypeMapper.Unmarshal([]byte(`{"correct": false}`), v)
}
<|endoftext|>


<commit_before>package keys

import (
	"fmt"
	"strings"

	"github.com/pkg/errors"
	crypto "github.com/tendermint/go-crypto"
	"github.com/tendermint/go-crypto/keys/words"
	dbm "github.com/tendermint/tmlibs/db"
)

// dbKeybase combines encyption and storage implementation to provide
// a full-featured key manager
type dbKeybase struct {
	db    dbm.DB
	codec words.Codec
}

func New(db dbm.DB, codec words.Codec) dbKeybase {
	return dbKeybase{
		db:    db,
		codec: codec,
	}
}

var _ Keybase = dbKeybase{}

// Create generates a new key and persists it to storage, encrypted
// using the passphrase.  It returns the generated seedphrase
// (mnemonic) and the key Info.  It returns an error if it fails to
// generate a key for the given algo type, or if another key is
// already stored under the same name.
func (kb dbKeybase) Create(name, passphrase string, algo CryptoAlgo) (Info, string, error) {
	// NOTE: secret is SHA256 hashed by secp256k1 and ed25519.
	// 16 byte secret corresponds to 12 BIP39 words.
	// XXX: Ledgers use 24 words now - should we ?
	secret := crypto.CRandBytes(16)
	priv, err := generate(algo, secret)
	if err != nil {
		return Info{}, "", err
	}

	// encrypt and persist the key
	info := kb.writeKey(priv, name, passphrase)

	// we append the type byte to the serialized secret to help with
	// recovery
	// ie [secret] = [type] + [secret]
	typ := cryptoAlgoToByte(algo)
	secret = append([]byte{typ}, secret...)

	// return the mnemonic phrase
	words, err := kb.codec.BytesToWords(secret)
	seed := strings.Join(words, " ")
	return info, seed, err
}

// Recover converts a seedphrase to a private key and persists it,
// encrypted with the given passphrase.  Functions like Create, but
// seedphrase is input not output.
func (kb dbKeybase) Recover(name, passphrase, seedphrase string) (Info, error) {
	words := strings.Split(strings.TrimSpace(seedphrase), " ")
	secret, err := kb.codec.WordsToBytes(words)
	if err != nil {
		return Info{}, err
	}

	// secret is comprised of the actual secret with the type
	// appended.
	// ie [secret] = [type] + [secret]
	typ, secret := secret[0], secret[1:]
	algo := byteToCryptoAlgo(typ)
	priv, err := generate(algo, secret)
	if err != nil {
		return Info{}, err
	}

	// encrypt and persist key.
	public := kb.writeKey(priv, name, passphrase)
	return public, err
}

// List returns the keys from storage in alphabetical order.
func (kb dbKeybase) List() ([]Info, error) {
	var res []Info
	iter := kb.db.Iterator(nil, nil)
	defer iter.Close()
	for ; iter.Valid(); iter.Next() {
		// key := iter.Key()
		info, err := readInfo(iter.Value())
		if err != nil {
			return nil, err
		}
		res = append(res, info)
	}
	return res, nil
}

// Get returns the public information about one key.
func (kb dbKeybase) Get(name string) (Info, error) {
	bs := kb.db.Get(infoKey(name))
	return readInfo(bs)
}

// Sign signs the msg with the named key.
// It returns an error if the key doesn't exist or the decryption fails.
func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig crypto.Signature, pub crypto.PubKey, err error) {
	info, err := kb.Get(name)
	if err != nil {
		return
	}
	if info.PrivKeyArmor == "" {
		err = fmt.Errorf("private key not available")
		return
	}
	priv, err := unarmorDecryptPrivKey(info.PrivKeyArmor, passphrase)
	if err != nil {
		return
	}
	sig = priv.Sign(msg)
	pub = priv.PubKey()
	return
}

func (kb dbKeybase) Export(name string) (armor string, err error) {
	bz := kb.db.Get(infoKey(name))
	if bz == nil {
		return "", errors.New("No key to export with name " + name)
	}
	return armorInfoBytes(bz), nil
}

// ExportPubKey returns public keys in ASCII armored format.
// Retrieve a Info object by its name and return the public key in
// a portable format.
func (kb dbKeybase) ExportPubKey(name string) (armor string, err error) {
	bz := kb.db.Get(infoKey(name))
	if bz == nil {
		return "", errors.New("No key to export with name " + name)
	}
	info, err := readInfo(bz)
	if err != nil {
		return
	}
	return armorPubKeyBytes(info.PubKey.Bytes()), nil
}

func (kb dbKeybase) Import(name string, armor string) (err error) {
	bz := kb.db.Get(infoKey(name))
	if len(bz) > 0 {
		return errors.New("Cannot overwrite data for name " + name)
	}
	infoBytes, err := unarmorInfoBytes(armor)
	if err != nil {
		return
	}
	kb.db.Set(infoKey(name), infoBytes)
	return nil
}

// ExportPubKey imports ASCII-armored public keys.
// Store a new Info object holding a public key only, i.e. it will
// not be possible to sign with it as it lacks the secret key.
func (kb dbKeybase) ImportPubKey(name string, armor string) (err error) {
	bz := kb.db.Get(infoKey(name))
	if len(bz) > 0 {
		return errors.New("Cannot overwrite data for name " + name)
	}
	pubBytes, err := unarmorPubKeyBytes(armor)
	if err != nil {
		return
	}
	pubKey, err := crypto.PubKeyFromBytes(pubBytes)
	if err != nil {
		return
	}
	kb.writePubKey(pubKey, name)
	return
}

// Delete removes key forever, but we must present the
// proper passphrase before deleting it (for security).
func (kb dbKeybase) Delete(name, passphrase string) error {
	// verify we have the proper password before deleting
	info, err := kb.Get(name)
	if err != nil {
		return err
	}
	_, err = unarmorDecryptPrivKey(info.PrivKeyArmor, passphrase)
	if err != nil {
		return err
	}
	kb.db.DeleteSync(infoKey(name))
	return nil
}

// Update changes the passphrase with which an already stored key is
// encrypted.
//
// oldpass must be the current passphrase used for encryption,
// newpass will be the only valid passphrase from this time forward.
func (kb dbKeybase) Update(name, oldpass, newpass string) error {
	info, err := kb.Get(name)
	if err != nil {
		return err
	}
	key, err := unarmorDecryptPrivKey(info.PrivKeyArmor, oldpass)
	if err != nil {
		return err
	}

	kb.writeKey(key, name, newpass)
	return nil
}

func (kb dbKeybase) writePubKey(pub crypto.PubKey, name string) Info {
	// make Info
	info := newInfo(name, pub, "")

	// write them both
	kb.db.SetSync(infoKey(name), info.bytes())
	return info
}

func (kb dbKeybase) writeKey(priv crypto.PrivKey, name, passphrase string) Info {
	// generate the encrypted privkey
	privArmor := encryptArmorPrivKey(priv, passphrase)
	// make Info
	info := newInfo(name, priv.PubKey(), privArmor)

	// write them both
	kb.db.SetSync(infoKey(name), info.bytes())
	return info
}

func generate(algo CryptoAlgo, secret []byte) (crypto.PrivKey, error) {
	switch algo {
	case AlgoEd25519:
		return crypto.GenPrivKeyEd25519FromSecret(secret), nil
	case AlgoSecp256k1:
		return crypto.GenPrivKeySecp256k1FromSecret(secret), nil
	default:
		err := errors.Errorf("Cannot generate keys for algorithm: %s", algo)
		return nil, err
	}
}

func infoKey(name string) []byte {
	return []byte(fmt.Sprintf("%s.info", name))
}
<commit_msg>fix comment<commit_after>package keys

import (
	"fmt"
	"strings"

	"github.com/pkg/errors"
	crypto "github.com/tendermint/go-crypto"
	"github.com/tendermint/go-crypto/keys/words"
	dbm "github.com/tendermint/tmlibs/db"
)

// dbKeybase combines encyption and storage implementation to provide
// a full-featured key manager
type dbKeybase struct {
	db    dbm.DB
	codec words.Codec
}

func New(db dbm.DB, codec words.Codec) dbKeybase {
	return dbKeybase{
		db:    db,
		codec: codec,
	}
}

var _ Keybase = dbKeybase{}

// Create generates a new key and persists it to storage, encrypted
// using the passphrase.  It returns the generated seedphrase
// (mnemonic) and the key Info.  It returns an error if it fails to
// generate a key for the given algo type, or if another key is
// already stored under the same name.
func (kb dbKeybase) Create(name, passphrase string, algo CryptoAlgo) (Info, string, error) {
	// NOTE: secret is SHA256 hashed by secp256k1 and ed25519.
	// 16 byte secret corresponds to 12 BIP39 words.
	// XXX: Ledgers use 24 words now - should we ?
	secret := crypto.CRandBytes(16)
	priv, err := generate(algo, secret)
	if err != nil {
		return Info{}, "", err
	}

	// encrypt and persist the key
	info := kb.writeKey(priv, name, passphrase)

	// we append the type byte to the serialized secret to help with
	// recovery
	// ie [secret] = [type] + [secret]
	typ := cryptoAlgoToByte(algo)
	secret = append([]byte{typ}, secret...)

	// return the mnemonic phrase
	words, err := kb.codec.BytesToWords(secret)
	seed := strings.Join(words, " ")
	return info, seed, err
}

// Recover converts a seedphrase to a private key and persists it,
// encrypted with the given passphrase.  Functions like Create, but
// seedphrase is input not output.
func (kb dbKeybase) Recover(name, passphrase, seedphrase string) (Info, error) {
	words := strings.Split(strings.TrimSpace(seedphrase), " ")
	secret, err := kb.codec.WordsToBytes(words)
	if err != nil {
		return Info{}, err
	}

	// secret is comprised of the actual secret with the type
	// appended.
	// ie [secret] = [type] + [secret]
	typ, secret := secret[0], secret[1:]
	algo := byteToCryptoAlgo(typ)
	priv, err := generate(algo, secret)
	if err != nil {
		return Info{}, err
	}

	// encrypt and persist key.
	public := kb.writeKey(priv, name, passphrase)
	return public, err
}

// List returns the keys from storage in alphabetical order.
func (kb dbKeybase) List() ([]Info, error) {
	var res []Info
	iter := kb.db.Iterator(nil, nil)
	defer iter.Close()
	for ; iter.Valid(); iter.Next() {
		// key := iter.Key()
		info, err := readInfo(iter.Value())
		if err != nil {
			return nil, err
		}
		res = append(res, info)
	}
	return res, nil
}

// Get returns the public information about one key.
func (kb dbKeybase) Get(name string) (Info, error) {
	bs := kb.db.Get(infoKey(name))
	return readInfo(bs)
}

// Sign signs the msg with the named key.
// It returns an error if the key doesn't exist or the decryption fails.
func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig crypto.Signature, pub crypto.PubKey, err error) {
	info, err := kb.Get(name)
	if err != nil {
		return
	}
	if info.PrivKeyArmor == "" {
		err = fmt.Errorf("private key not available")
		return
	}
	priv, err := unarmorDecryptPrivKey(info.PrivKeyArmor, passphrase)
	if err != nil {
		return
	}
	sig = priv.Sign(msg)
	pub = priv.PubKey()
	return
}

func (kb dbKeybase) Export(name string) (armor string, err error) {
	bz := kb.db.Get(infoKey(name))
	if bz == nil {
		return "", errors.New("No key to export with name " + name)
	}
	return armorInfoBytes(bz), nil
}

// ExportPubKey returns public keys in ASCII armored format.
// Retrieve a Info object by its name and return the public key in
// a portable format.
func (kb dbKeybase) ExportPubKey(name string) (armor string, err error) {
	bz := kb.db.Get(infoKey(name))
	if bz == nil {
		return "", errors.New("No key to export with name " + name)
	}
	info, err := readInfo(bz)
	if err != nil {
		return
	}
	return armorPubKeyBytes(info.PubKey.Bytes()), nil
}

func (kb dbKeybase) Import(name string, armor string) (err error) {
	bz := kb.db.Get(infoKey(name))
	if len(bz) > 0 {
		return errors.New("Cannot overwrite data for name " + name)
	}
	infoBytes, err := unarmorInfoBytes(armor)
	if err != nil {
		return
	}
	kb.db.Set(infoKey(name), infoBytes)
	return nil
}

// ImportPubKey imports ASCII-armored public keys.
// Store a new Info object holding a public key only, i.e. it will
// not be possible to sign with it as it lacks the secret key.
func (kb dbKeybase) ImportPubKey(name string, armor string) (err error) {
	bz := kb.db.Get(infoKey(name))
	if len(bz) > 0 {
		return errors.New("Cannot overwrite data for name " + name)
	}
	pubBytes, err := unarmorPubKeyBytes(armor)
	if err != nil {
		return
	}
	pubKey, err := crypto.PubKeyFromBytes(pubBytes)
	if err != nil {
		return
	}
	kb.writePubKey(pubKey, name)
	return
}

// Delete removes key forever, but we must present the
// proper passphrase before deleting it (for security).
func (kb dbKeybase) Delete(name, passphrase string) error {
	// verify we have the proper password before deleting
	info, err := kb.Get(name)
	if err != nil {
		return err
	}
	_, err = unarmorDecryptPrivKey(info.PrivKeyArmor, passphrase)
	if err != nil {
		return err
	}
	kb.db.DeleteSync(infoKey(name))
	return nil
}

// Update changes the passphrase with which an already stored key is
// encrypted.
//
// oldpass must be the current passphrase used for encryption,
// newpass will be the only valid passphrase from this time forward.
func (kb dbKeybase) Update(name, oldpass, newpass string) error {
	info, err := kb.Get(name)
	if err != nil {
		return err
	}
	key, err := unarmorDecryptPrivKey(info.PrivKeyArmor, oldpass)
	if err != nil {
		return err
	}

	kb.writeKey(key, name, newpass)
	return nil
}

func (kb dbKeybase) writePubKey(pub crypto.PubKey, name string) Info {
	// make Info
	info := newInfo(name, pub, "")

	// write them both
	kb.db.SetSync(infoKey(name), info.bytes())
	return info
}

func (kb dbKeybase) writeKey(priv crypto.PrivKey, name, passphrase string) Info {
	// generate the encrypted privkey
	privArmor := encryptArmorPrivKey(priv, passphrase)
	// make Info
	info := newInfo(name, priv.PubKey(), privArmor)

	// write them both
	kb.db.SetSync(infoKey(name), info.bytes())
	return info
}

func generate(algo CryptoAlgo, secret []byte) (crypto.PrivKey, error) {
	switch algo {
	case AlgoEd25519:
		return crypto.GenPrivKeyEd25519FromSecret(secret), nil
	case AlgoSecp256k1:
		return crypto.GenPrivKeySecp256k1FromSecret(secret), nil
	default:
		err := errors.Errorf("Cannot generate keys for algorithm: %s", algo)
		return nil, err
	}
}

func infoKey(name string) []byte {
	return []byte(fmt.Sprintf("%s.info", name))
}
<|endoftext|>


<commit_before>package lfucache

import (
	"errors"
)

// Cache is an LFU cache structure.
type Cache struct {
	capacity      int
	length        int
	frequencyList *frequencyNode
	index         map[interface{}]*node
	evictedChans  []chan<- interface{}
	stats         Statistics
}

// Statistics contains current item counts and operation counters.
type Statistics struct {
	LenFreq0    int // Number of items at frequency zero, i.e Inserted but not Accessed
	Inserts     int // Number of Insert()s
	Hits        int // Number of hits (Access() to item)
	Misses      int // Number of misses (Access() to non-existant key)
	Evictions   int // Number of evictions (due to size constraints on Insert(), or EvictIf() calls)
	Deletes     int // Number of Delete()s.
	FreqListLen int // Current length of frequency list, i.e. the number of distinct usage levels
}

// The "frequencyNode" and "node" types make up the two levels of linked lists
// that we use to keep track of the usage per node. It could be argued that we
// should use the built in list type instead of implementing the linked list
// structure again directly in the nodes. I tried that and while it results in
// slightly less code, the extra layer of indirection and resulting type
// assertions make the code less readable. Also the Access() method becomes
// several times slower and requires a heap allocation per call. All in all,
// this was preferable.

type frequencyNode struct {
	usage int
	prev  *frequencyNode
	next  *frequencyNode
	head  *node
	tail  *node // most recently inserted
}

type node struct {
	key    interface{}
	value  interface{}
	parent *frequencyNode
	next   *node
	prev   *node
}

var (
	errZeroSizeCache = errors.New("create zero-sized cache")
	errEmptyLFU      = errors.New("lfu on empty cache")
)

// New initializes a new LFU Cache structure with the specified capacity.
func New(capacity int) *Cache {
	if capacity == 0 {
		panic(errZeroSizeCache)
	}

	return &Cache{
		capacity:      capacity,
		index:         make(map[interface{}]*node, capacity),
		frequencyList: &frequencyNode{},
	}
}

// Resize the cache to a new capacity. When shrinking, items may get evicted.
func (c *Cache) Resize(capacity int) {
	c.capacity = capacity
	for c.length > c.capacity {
		c.evict(c.lfu())
	}
}

// Insert inserts an item into the cache. If the key already exists, the
// existing item is evicted and the new one inserted. The key type is
// restricted to those acceptable as map keys
// (http://golang.org/ref/spec#Map_types).
func (c *Cache) Insert(key interface{}, value interface{}) {
	if debug {
		c.check()
	}

	if n, ok := c.index[key]; ok {
		c.evict(n)
	}

	if c.length == c.capacity {
		c.evict(c.lfu())
	}

	n := &node{key: key, value: value}
	c.index[key] = n
	c.moveNodeToFn(n, c.frequencyList)
	c.length++
	c.stats.Inserts++

	if debug {
		c.check()
	}
}

// Delete deletes an item from the cache and returns true. Does nothing and
// returns false if the key was not present in the cache.
func (c *Cache) Delete(key interface{}) bool {
	if debug {
		c.check()
	}

	n, ok := c.index[key]
	if ok {
		c.deleteNode(n)
		c.stats.Deletes++
	}

	if debug {
		c.check()
	}

	return ok
}

// Access an item in the cache. Returns "value, ok" similar to map indexing.
// Increases the item's use count.
func (c *Cache) Access(key interface{}) (interface{}, bool) {
	if debug {
		c.check()
	}

	n, ok := c.index[key]
	if !ok {
		c.stats.Misses++
		return nil, false
	}

	nextUsage := n.parent.usage + 1
	var nextFn *frequencyNode
	if n.parent.next == nil || n.parent.next.usage != nextUsage {
		nextFn = c.newFrequencyNode(nextUsage, n.parent)
	} else {
		nextFn = n.parent.next
	}

	c.moveNodeToFn(n, nextFn)
	c.stats.Hits++

	if debug {
		c.check()
	}

	return n.value, true
}

// Len returns the number of items currently stored in the cache.
func (c *Cache) Len() int {
	return c.length
}

// Cap returns the maximum number of items the cache will hold.
func (c *Cache) Cap() int {
	return c.capacity
}

// Statistics returns the cache statistics.
func (c *Cache) Statistics() Statistics {
	if debug {
		c.check()
	}

	c.stats.LenFreq0 = c.items0()
	c.stats.FreqListLen = c.numFrequencyNodes()
	return c.stats
}

// Evictions registers a channel used to report items that get evicted from
// the cache.  Only items evicted due to LFU or EvictIf() will be sent on the
// channel, not items removed by calling Delete(). The channel must be
// unregistered using UnregisterEvictions() prior to ceasing reads in order to
// avoid deadlocking evictions.
func (c *Cache) Evictions(e chan<- interface{}) {
	if debug {
		c.check()
	}

	c.evictedChans = append(c.evictedChans, e)
}

// UnregisterEvictions removes the channel from the list of channels to be
// notified on item eviction. Must be called when there is no longer a reader
// for the channel in question.
func (c *Cache) UnregisterEvictions(e chan<- interface{}) {
	if debug {
		c.check()
	}

	var i int
	var found bool

	for i = range c.evictedChans {
		if c.evictedChans[i] == e {
			found = true
			break
		}
	}

	if found {
		copy(c.evictedChans[i:], c.evictedChans[i+1:])
		c.evictedChans[len(c.evictedChans)-1] = nil
		c.evictedChans = c.evictedChans[:len(c.evictedChans)-1]
	}
}

// EvictIf applies test to each item in the cache and evicts it if the test
// returns true.  Returns the number of items that were evicted.
func (c *Cache) EvictIf(test func(interface{}) bool) int {
	if debug {
		c.check()
	}

	cnt := 0
	for _, n := range c.index {
		if test(n.value) {
			c.evict(n)
			cnt++
		}
	}

	if debug {
		c.check()
	}

	return cnt
}

// evict evicts a node from the cache by removing it from the structure and
// notifying any interested eviction listeners
func (c *Cache) evict(n *node) {
	for i := range c.evictedChans {
		c.evictedChans[i] <- n.value
	}
	c.deleteNode(n)
	c.stats.Evictions++
}

// deleteNode deletes a node from the cache, also deleting the frequency node
// if it became empty
func (c *Cache) deleteNode(n *node) {
	if n.prev != nil {
		n.prev.next = n.next
	}

	if n.next != nil {
		n.next.prev = n.prev
	}

	fn := n.parent
	if fn.head == n {
		fn.head = n.next
	}
	if fn.tail == n {
		fn.tail = n.prev
	}

	if fn.usage != 0 && fn.head == nil {
		c.deleteFrequencyNode(fn)
	}

	delete(c.index, n.key)
	c.length--
}

// lfu returns the least frequently used node in the cache, prefering the
// oldest if there are multiple nodes with the same lowest usage count
func (c *Cache) lfu() *node {
	for fn := c.frequencyList; fn != nil; fn = fn.next {
		if fn.head != nil {
			return fn.head
		}
	}

	panic(errEmptyLFU)
}

// newFrequencyNode inserts a new frequency node after the specified prev node
func (c *Cache) newFrequencyNode(usage int, prev *frequencyNode) *frequencyNode {
	fn := &frequencyNode{
		usage: usage,
		prev:  prev,
		next:  prev.next,
	}

	if fn.next != nil {
		fn.next.prev = fn
	}

	prev.next = fn

	return fn
}

// deleteFrequencyNode removes a new frequency node from the list
func (c *Cache) deleteFrequencyNode(fn *frequencyNode) {
	if fn.next != nil {
		fn.next.prev = fn.prev
	}

	fn.prev.next = fn.next
}

// moveNodeToFn moves a node to become a child of a frequency node, while
// properly removing it from any current frequency node
func (c *Cache) moveNodeToFn(n *node, fn *frequencyNode) {
	if n.prev != nil {
		n.prev.next = n.next
	}

	if n.next != nil {
		n.next.prev = n.prev
	}

	if n.parent != nil {
		if n.parent.head == n {
			n.parent.head = n.next
		}
		if n.parent.tail == n {
			n.parent.tail = n.prev
		}
		if n.parent.head == nil && n.parent.usage != 0 {
			c.deleteFrequencyNode(n.parent)
		}
	}

	n.prev = nil
	n.next = nil

	n.parent = fn
	if fn.tail != nil {
		n.prev = fn.tail
		n.prev.next = n
	}

	if fn.head == nil {
		fn.head = n
	}

	fn.tail = n
}

// items0 returns the number of items at the head of the node list (usage
// count zero)
func (c *Cache) items0() (count int) {
	for n := c.frequencyList.head; n != nil; n = n.next {
		count++
	}
	return
}

// numFrequencyNodes returns the number of frequency nodes in the cache
func (c *Cache) numFrequencyNodes() (count int) {
	for fn := c.frequencyList; fn != nil; fn = fn.next {
		count++
	}
	return
}
<commit_msg>Actually deprecate even more<commit_after>package lfucache // import "github.com/calmh/deprecated_lfucache"

import (
	"errors"
)

// Cache is an LFU cache structure.
type Cache struct {
	capacity      int
	length        int
	frequencyList *frequencyNode
	index         map[interface{}]*node
	evictedChans  []chan<- interface{}
	stats         Statistics
}

// Statistics contains current item counts and operation counters.
type Statistics struct {
	LenFreq0    int // Number of items at frequency zero, i.e Inserted but not Accessed
	Inserts     int // Number of Insert()s
	Hits        int // Number of hits (Access() to item)
	Misses      int // Number of misses (Access() to non-existant key)
	Evictions   int // Number of evictions (due to size constraints on Insert(), or EvictIf() calls)
	Deletes     int // Number of Delete()s.
	FreqListLen int // Current length of frequency list, i.e. the number of distinct usage levels
}

// The "frequencyNode" and "node" types make up the two levels of linked lists
// that we use to keep track of the usage per node. It could be argued that we
// should use the built in list type instead of implementing the linked list
// structure again directly in the nodes. I tried that and while it results in
// slightly less code, the extra layer of indirection and resulting type
// assertions make the code less readable. Also the Access() method becomes
// several times slower and requires a heap allocation per call. All in all,
// this was preferable.

type frequencyNode struct {
	usage int
	prev  *frequencyNode
	next  *frequencyNode
	head  *node
	tail  *node // most recently inserted
}

type node struct {
	key    interface{}
	value  interface{}
	parent *frequencyNode
	next   *node
	prev   *node
}

var (
	errZeroSizeCache = errors.New("create zero-sized cache")
	errEmptyLFU      = errors.New("lfu on empty cache")
)

// New initializes a new LFU Cache structure with the specified capacity.
func New(capacity int) *Cache {
	if capacity == 0 {
		panic(errZeroSizeCache)
	}

	return &Cache{
		capacity:      capacity,
		index:         make(map[interface{}]*node, capacity),
		frequencyList: &frequencyNode{},
	}
}

// Resize the cache to a new capacity. When shrinking, items may get evicted.
func (c *Cache) Resize(capacity int) {
	c.capacity = capacity
	for c.length > c.capacity {
		c.evict(c.lfu())
	}
}

// Insert inserts an item into the cache. If the key already exists, the
// existing item is evicted and the new one inserted. The key type is
// restricted to those acceptable as map keys
// (http://golang.org/ref/spec#Map_types).
func (c *Cache) Insert(key interface{}, value interface{}) {
	if debug {
		c.check()
	}

	if n, ok := c.index[key]; ok {
		c.evict(n)
	}

	if c.length == c.capacity {
		c.evict(c.lfu())
	}

	n := &node{key: key, value: value}
	c.index[key] = n
	c.moveNodeToFn(n, c.frequencyList)
	c.length++
	c.stats.Inserts++

	if debug {
		c.check()
	}
}

// Delete deletes an item from the cache and returns true. Does nothing and
// returns false if the key was not present in the cache.
func (c *Cache) Delete(key interface{}) bool {
	if debug {
		c.check()
	}

	n, ok := c.index[key]
	if ok {
		c.deleteNode(n)
		c.stats.Deletes++
	}

	if debug {
		c.check()
	}

	return ok
}

// Access an item in the cache. Returns "value, ok" similar to map indexing.
// Increases the item's use count.
func (c *Cache) Access(key interface{}) (interface{}, bool) {
	if debug {
		c.check()
	}

	n, ok := c.index[key]
	if !ok {
		c.stats.Misses++
		return nil, false
	}

	nextUsage := n.parent.usage + 1
	var nextFn *frequencyNode
	if n.parent.next == nil || n.parent.next.usage != nextUsage {
		nextFn = c.newFrequencyNode(nextUsage, n.parent)
	} else {
		nextFn = n.parent.next
	}

	c.moveNodeToFn(n, nextFn)
	c.stats.Hits++

	if debug {
		c.check()
	}

	return n.value, true
}

// Len returns the number of items currently stored in the cache.
func (c *Cache) Len() int {
	return c.length
}

// Cap returns the maximum number of items the cache will hold.
func (c *Cache) Cap() int {
	return c.capacity
}

// Statistics returns the cache statistics.
func (c *Cache) Statistics() Statistics {
	if debug {
		c.check()
	}

	c.stats.LenFreq0 = c.items0()
	c.stats.FreqListLen = c.numFrequencyNodes()
	return c.stats
}

// Evictions registers a channel used to report items that get evicted from
// the cache.  Only items evicted due to LFU or EvictIf() will be sent on the
// channel, not items removed by calling Delete(). The channel must be
// unregistered using UnregisterEvictions() prior to ceasing reads in order to
// avoid deadlocking evictions.
func (c *Cache) Evictions(e chan<- interface{}) {
	if debug {
		c.check()
	}

	c.evictedChans = append(c.evictedChans, e)
}

// UnregisterEvictions removes the channel from the list of channels to be
// notified on item eviction. Must be called when there is no longer a reader
// for the channel in question.
func (c *Cache) UnregisterEvictions(e chan<- interface{}) {
	if debug {
		c.check()
	}

	var i int
	var found bool

	for i = range c.evictedChans {
		if c.evictedChans[i] == e {
			found = true
			break
		}
	}

	if found {
		copy(c.evictedChans[i:], c.evictedChans[i+1:])
		c.evictedChans[len(c.evictedChans)-1] = nil
		c.evictedChans = c.evictedChans[:len(c.evictedChans)-1]
	}
}

// EvictIf applies test to each item in the cache and evicts it if the test
// returns true.  Returns the number of items that were evicted.
func (c *Cache) EvictIf(test func(interface{}) bool) int {
	if debug {
		c.check()
	}

	cnt := 0
	for _, n := range c.index {
		if test(n.value) {
			c.evict(n)
			cnt++
		}
	}

	if debug {
		c.check()
	}

	return cnt
}

// evict evicts a node from the cache by removing it from the structure and
// notifying any interested eviction listeners
func (c *Cache) evict(n *node) {
	for i := range c.evictedChans {
		c.evictedChans[i] <- n.value
	}
	c.deleteNode(n)
	c.stats.Evictions++
}

// deleteNode deletes a node from the cache, also deleting the frequency node
// if it became empty
func (c *Cache) deleteNode(n *node) {
	if n.prev != nil {
		n.prev.next = n.next
	}

	if n.next != nil {
		n.next.prev = n.prev
	}

	fn := n.parent
	if fn.head == n {
		fn.head = n.next
	}
	if fn.tail == n {
		fn.tail = n.prev
	}

	if fn.usage != 0 && fn.head == nil {
		c.deleteFrequencyNode(fn)
	}

	delete(c.index, n.key)
	c.length--
}

// lfu returns the least frequently used node in the cache, prefering the
// oldest if there are multiple nodes with the same lowest usage count
func (c *Cache) lfu() *node {
	for fn := c.frequencyList; fn != nil; fn = fn.next {
		if fn.head != nil {
			return fn.head
		}
	}

	panic(errEmptyLFU)
}

// newFrequencyNode inserts a new frequency node after the specified prev node
func (c *Cache) newFrequencyNode(usage int, prev *frequencyNode) *frequencyNode {
	fn := &frequencyNode{
		usage: usage,
		prev:  prev,
		next:  prev.next,
	}

	if fn.next != nil {
		fn.next.prev = fn
	}

	prev.next = fn

	return fn
}

// deleteFrequencyNode removes a new frequency node from the list
func (c *Cache) deleteFrequencyNode(fn *frequencyNode) {
	if fn.next != nil {
		fn.next.prev = fn.prev
	}

	fn.prev.next = fn.next
}

// moveNodeToFn moves a node to become a child of a frequency node, while
// properly removing it from any current frequency node
func (c *Cache) moveNodeToFn(n *node, fn *frequencyNode) {
	if n.prev != nil {
		n.prev.next = n.next
	}

	if n.next != nil {
		n.next.prev = n.prev
	}

	if n.parent != nil {
		if n.parent.head == n {
			n.parent.head = n.next
		}
		if n.parent.tail == n {
			n.parent.tail = n.prev
		}
		if n.parent.head == nil && n.parent.usage != 0 {
			c.deleteFrequencyNode(n.parent)
		}
	}

	n.prev = nil
	n.next = nil

	n.parent = fn
	if fn.tail != nil {
		n.prev = fn.tail
		n.prev.next = n
	}

	if fn.head == nil {
		fn.head = n
	}

	fn.tail = n
}

// items0 returns the number of items at the head of the node list (usage
// count zero)
func (c *Cache) items0() (count int) {
	for n := c.frequencyList.head; n != nil; n = n.next {
		count++
	}
	return
}

// numFrequencyNodes returns the number of frequency nodes in the cache
func (c *Cache) numFrequencyNodes() (count int) {
	for fn := c.frequencyList; fn != nil; fn = fn.next {
		count++
	}
	return
}
<|endoftext|>


<commit_before>package auth

import (
	"strings"

	v1 "k8s.io/api/rbac/v1"
	meta2 "k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/apimachinery/pkg/runtime"
)

func rbByOwner(rb *v1.RoleBinding) ([]string, error) {
	return getRBOwnerKey(rb), nil
}

func getRBOwnerKey(rb *v1.RoleBinding) []string {
	var owners []string
	for _, o := range rb.OwnerReferences {
		owners = append(owners, string(o.UID))
	}
	return owners
}

func rbRoleSubjectKey(roleName string, subject v1.Subject) string {
	return roleName + "." + subject.Kind + "." + subject.Name

}

func rbRoleSubjectKeys(roleName string, subjects []v1.Subject) []string {
	var keys []string
	for _, s := range subjects {
		keys = append(keys, rbRoleSubjectKey(roleName, s))
	}
	return keys
}

func indexByMembershipBindingOwner(obj interface{}) ([]string, error) {
	obj, ok := obj.(runtime.Object)
	if !ok {
		return []string{}, nil
	}

	meta, err := meta2.Accessor(obj)
	if err != nil {
		return nil, err
	}

	ns := meta.GetNamespace()
	var keys []string
	for k, v := range meta.GetLabels() {
		if v == membershipBindingOwner {
			keys = append(keys, strings.Join([]string{ns, k}, "/"))
		}
	}

	return keys, nil
}

func rbByClusterRoleAndSubject(rb *v1.ClusterRoleBinding) ([]string, error) {
	var subjects []v1.Subject
	var roleName string

	roleName = rb.RoleRef.Name
	subjects = rb.Subjects
	return rbRoleSubjectKeys(roleName, subjects), nil
}

func rbByRoleAndSubject(rb *v1.RoleBinding) ([]string, error) {
	var subjects []v1.Subject
	var roleName string

	roleName = rb.RoleRef.Name
	subjects = rb.Subjects

	return rbRoleSubjectKeys(roleName, subjects), nil
}
<commit_msg>Ignore Accessor error in role index<commit_after>package auth

import (
	"strings"

	"github.com/sirupsen/logrus"
	v1 "k8s.io/api/rbac/v1"
	meta2 "k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/apimachinery/pkg/runtime"
)

func rbByOwner(rb *v1.RoleBinding) ([]string, error) {
	return getRBOwnerKey(rb), nil
}

func getRBOwnerKey(rb *v1.RoleBinding) []string {
	var owners []string
	for _, o := range rb.OwnerReferences {
		owners = append(owners, string(o.UID))
	}
	return owners
}

func rbRoleSubjectKey(roleName string, subject v1.Subject) string {
	return roleName + "." + subject.Kind + "." + subject.Name

}

func rbRoleSubjectKeys(roleName string, subjects []v1.Subject) []string {
	var keys []string
	for _, s := range subjects {
		keys = append(keys, rbRoleSubjectKey(roleName, s))
	}
	return keys
}

func indexByMembershipBindingOwner(obj interface{}) ([]string, error) {
	ro, ok := obj.(runtime.Object)
	if !ok {
		return []string{}, nil
	}

	meta, err := meta2.Accessor(ro)
	if err != nil {
		logrus.Warnf("[indexByMembershipBindingOwner] unexpected object type: %T, err: %v", obj, err.Error())
		return []string{}, nil
	}

	ns := meta.GetNamespace()
	var keys []string
	for k, v := range meta.GetLabels() {
		if v == membershipBindingOwner {
			keys = append(keys, strings.Join([]string{ns, k}, "/"))
		}
	}

	return keys, nil
}

func rbByClusterRoleAndSubject(rb *v1.ClusterRoleBinding) ([]string, error) {
	var subjects []v1.Subject
	var roleName string

	roleName = rb.RoleRef.Name
	subjects = rb.Subjects
	return rbRoleSubjectKeys(roleName, subjects), nil
}

func rbByRoleAndSubject(rb *v1.RoleBinding) ([]string, error) {
	var subjects []v1.Subject
	var roleName string

	roleName = rb.RoleRef.Name
	subjects = rb.Subjects

	return rbRoleSubjectKeys(roleName, subjects), nil
}
<|endoftext|>


<commit_before>package mngo

import (
	"fmt"
	"net/http/httptest"
	"testing"
)

var testCases = []struct {
	link  string
	hash  string
	isURL bool
}{
	{"abcdef", "", false},
	{"www.globo.com", "", false},
	{"www.notvalidurl.netscape", "", false},
	{"http://www.gorillatoolkit.org/pkg/mux", "70df8650c03c9fdfc959f04a64ecd956", true},
	{"https://mail.google.com/mail/u/0/#inbox", "2122c5656da3d86d77c08f7af48c0268", true},
	{"https://mail.google.com/mail/u/0/#inbox", "2122c5656da3d86d77c08f7af48c0268", true},
	{"https://www.youtube.com/watch?v=grwx4OMfAn4", "678989a28d9b88ada6cc6678df8e6aa1", true},
}

func TestInsert(t *testing.T) {
	fmt.Print("Test Insert: ")
	r := httptest.NewRequest("POST", "/link/add", nil)
	w := httptest.NewRecorder()

	for _, test := range testCases {
		err := Insert(test.link, w, r)
		checkError(err)
		fmt.Print(".")
	}
	fmt.Println()
}

func TestFindHash(t *testing.T) {
	fmt.Print("Test FindHash: ")
	for _, test := range testCases {
		link, err := FindHash(test.hash)
		if err != nil && test.isURL == false {
			fmt.Print(".")
		}
		if test.link == link {
			fmt.Print(".")
		}
	}
}

func TestGetAll(t *testing.T) {
	fmt.Print("Test GetAll: ")
	a, err := GetAll()
	checkError(err)
	if len(a) == 2 {
		fmt.Print("..")
	} else {
		fmt.Println()
		fmt.Printf("Array bigger than expected, len == %v", len(a))
	}
	fmt.Println()
}

func TestDelete(t *testing.T) {
	fmt.Print("Test Delete: ")
	for _, test := range testCases {
		err := Delete(test.hash)
		if err != nil {
			continue
		}
		fmt.Print(".")
	}
	fmt.Println()
}
<commit_msg>better error msg<commit_after>package mngo

import (
	"fmt"
	"net/http/httptest"
	"testing"
)

var testCases = []struct {
	link  string
	hash  string
	isURL bool
}{
	{"abcdef", "", false},
	{"www.globo.com", "", false},
	{"www.notvalidurl.netscape", "", false},
	{"http://www.gorillatoolkit.org/pkg/mux", "70df8650c03c9fdfc959f04a64ecd956", true},
	{"https://mail.google.com/mail/u/0/#inbox", "2122c5656da3d86d77c08f7af48c0268", true},
	{"https://mail.google.com/mail/u/0/#inbox", "2122c5656da3d86d77c08f7af48c0268", true},
	{"https://www.youtube.com/watch?v=grwx4OMfAn4", "678989a28d9b88ada6cc6678df8e6aa1", true},
}

func TestInsert(t *testing.T) {
	fmt.Print("Test Insert: ")
	r := httptest.NewRequest("POST", "/link/add", nil)
	w := httptest.NewRecorder()

	for _, test := range testCases {
		err := Insert(test.link, w, r)
		checkError(err)
		fmt.Print(".")
	}
	fmt.Println()
}

func TestFindHash(t *testing.T) {
	fmt.Print("Test FindHash: ")
	for _, test := range testCases {
		link, err := FindHash(test.hash)
		if err != nil && test.isURL == false {
			fmt.Print(".")
			continue
		}
		if test.link == link {
			fmt.Print(".")
		} else {
			fmt.Printf("Link %s expected but %s found", test.link, link)
		}
		continue
	}
	fmt.Println()
}

func TestGetAll(t *testing.T) {
	fmt.Print("Test GetAll: ")
	a, err := GetAll()
	checkError(err)
	if len(a) == 3 {
		fmt.Print("...")
	} else {
		fmt.Println()
		fmt.Printf("Array bigger than expected, len == %v", len(a))
	}
	fmt.Println()
}

func TestDelete(t *testing.T) {
	fmt.Print("Test Delete: ")
	for _, test := range testCases {
		err := Delete(test.hash)
		if err != nil {
			continue
		}
		fmt.Print(".")
	}
	fmt.Println()
}
<|endoftext|>


<commit_before>package joule

import (
	"errors"
	"os"
	"strconv"
	"fmt"

	multierror "github.com/hashicorp/go-multierror"
	"gobot.io/x/gobot"
	"gobot.io/x/gobot/sysfs"
	"gobot.io/x/gobot/drivers/i2c"
)

func writeFile(path string, data []byte) (i int, err error) {
	file, err := sysfs.OpenFile(path, os.O_WRONLY, 0644)
	defer file.Close()
	if err != nil {
		return
	}

	return file.Write(data)
}

func readFile(path string) ([]byte, error) {
	file, err := sysfs.OpenFile(path, os.O_RDONLY, 0644)
	defer file.Close()
	if err != nil {
		return make([]byte, 0), err
	}

	buf := make([]byte, 200)
	var i = 0
	i, err = file.Read(buf)
	if i == 0 {
		return buf, err
	}
	return buf[:i], err
}

type sysfsPin struct {
	pin    int
	pwmPin int
}

// Adaptor represents an Intel Joule
type Adaptor struct {
	name        string
	digitalPins map[int]sysfs.DigitalPin
	pwmPins     map[int]*pwmPin
	i2cBuses    [3]sysfs.I2cDevice
	connect     func(e *Adaptor) (err error)
}

// NewAdaptor returns a new Joule Adaptor
func NewAdaptor() *Adaptor {
	return &Adaptor{
		name: gobot.DefaultName("Joule"),
		connect: func(e *Adaptor) (err error) {
			return
		},
	}
}

// Name returns the Adaptors name
func (e *Adaptor) Name() string { return e.name }

// SetName sets the Adaptors name
func (e *Adaptor) SetName(n string) { e.name = n }

// Connect initializes the Joule for use with the Arduino beakout board
func (e *Adaptor) Connect() (err error) {
	e.digitalPins = make(map[int]sysfs.DigitalPin)
	e.pwmPins = make(map[int]*pwmPin)
	err = e.connect(e)
	return
}

// Finalize releases all i2c devices and exported digital and pwm pins.
func (e *Adaptor) Finalize() (err error) {
	for _, pin := range e.digitalPins {
		if pin != nil {
			if errs := pin.Unexport(); errs != nil {
				err = multierror.Append(err, errs)
			}
		}
	}
	for _, pin := range e.pwmPins {
		if pin != nil {
			if errs := pin.enable("0"); errs != nil {
				err = multierror.Append(err, errs)
			}
			if errs := pin.unexport(); errs != nil {
				err = multierror.Append(err, errs)
			}
		}
	}
	for _, bus := range e.i2cBuses {
		if bus != nil {
			if errs := bus.Close(); errs != nil {
				err = multierror.Append(err, errs)
			}
		}
	}
	return
}

// digitalPin returns matched digitalPin for specified values
func (e *Adaptor) digitalPin(pin string, dir string) (sysfsPin sysfs.DigitalPin, err error) {
	i := sysfsPinMap[pin]
	if e.digitalPins[i.pin] == nil {
		e.digitalPins[i.pin] = sysfs.NewDigitalPin(i.pin)
		if err = e.digitalPins[i.pin].Export(); err != nil {
			// TODO: log error
			return
		}
	}

	if dir == "in" {
		if err = e.digitalPins[i.pin].Direction(sysfs.IN); err != nil {
			return
		}
	} else if dir == "out" {
		if err = e.digitalPins[i.pin].Direction(sysfs.OUT); err != nil {
			return
		}
	}
	return e.digitalPins[i.pin], nil
}

// DigitalRead reads digital value from pin
func (e *Adaptor) DigitalRead(pin string) (i int, err error) {
	sysfsPin, err := e.digitalPin(pin, "in")
	if err != nil {
		return
	}
	return sysfsPin.Read()
}

// DigitalWrite writes a value to the pin. Acceptable values are 1 or 0.
func (e *Adaptor) DigitalWrite(pin string, val byte) (err error) {
	sysfsPin, err := e.digitalPin(pin, "out")
	if err != nil {
		return
	}
	return sysfsPin.Write(int(val))
}

// PwmWrite writes the 0-254 value to the specified pin
func (e *Adaptor) PwmWrite(pin string, val byte) (err error) {
	sysPin := sysfsPinMap[pin]
	if sysPin.pwmPin != -1 {
		if e.pwmPins[sysPin.pwmPin] == nil {
			if err = e.DigitalWrite(pin, 1); err != nil {
				return
			}
			e.pwmPins[sysPin.pwmPin] = newPwmPin(sysPin.pwmPin)
			if err = e.pwmPins[sysPin.pwmPin].export(); err != nil {
				return
			}
			if err = e.pwmPins[sysPin.pwmPin].enable("1"); err != nil {
				return
			}
		}
		p, err := e.pwmPins[sysPin.pwmPin].period()
		if err != nil {
			return err
		}
		period, err := strconv.Atoi(p)
		if err != nil {
			return err
		}
		duty := gobot.FromScale(float64(val), 0, 255.0)
		return e.pwmPins[sysPin.pwmPin].writeDuty(strconv.Itoa(int(float64(period) * duty)))
	}
	return errors.New("Not a PWM pin")
}

// I2cGetConnection returns a connection to a device on a specified bus.
// Valid bus number is [0..2] which corresponds to /dev/i2c-0 through /dev/i2c-2.
func (c *Adaptor) I2cGetConnection(address int, bus int) (connection i2c.I2cConnection, err error) {
	if (bus < 0) || (bus > 2) {
		return nil, fmt.Errorf("Bus number %d out of range", bus)
	}
	if c.i2cBuses[bus] == nil {
		c.i2cBuses[bus], err = sysfs.NewI2cDevice(fmt.Sprintf("/dev/i2c-%d", bus))
	}
	return i2c.NewI2cConnection(c.i2cBuses[bus], address), err
}

func (c *Adaptor) I2cGetDefaultBus() int {
	return 0
}
<commit_msg>joule: cleanup order of included packages<commit_after>package joule

import (
	"errors"
	"fmt"
	"os"
	"strconv"

	multierror "github.com/hashicorp/go-multierror"
	"gobot.io/x/gobot"
	"gobot.io/x/gobot/drivers/i2c"
	"gobot.io/x/gobot/sysfs"
)

func writeFile(path string, data []byte) (i int, err error) {
	file, err := sysfs.OpenFile(path, os.O_WRONLY, 0644)
	defer file.Close()
	if err != nil {
		return
	}

	return file.Write(data)
}

func readFile(path string) ([]byte, error) {
	file, err := sysfs.OpenFile(path, os.O_RDONLY, 0644)
	defer file.Close()
	if err != nil {
		return make([]byte, 0), err
	}

	buf := make([]byte, 200)
	var i = 0
	i, err = file.Read(buf)
	if i == 0 {
		return buf, err
	}
	return buf[:i], err
}

type sysfsPin struct {
	pin    int
	pwmPin int
}

// Adaptor represents an Intel Joule
type Adaptor struct {
	name        string
	digitalPins map[int]sysfs.DigitalPin
	pwmPins     map[int]*pwmPin
	i2cBuses    [3]sysfs.I2cDevice
	connect     func(e *Adaptor) (err error)
}

// NewAdaptor returns a new Joule Adaptor
func NewAdaptor() *Adaptor {
	return &Adaptor{
		name: gobot.DefaultName("Joule"),
		connect: func(e *Adaptor) (err error) {
			return
		},
	}
}

// Name returns the Adaptors name
func (e *Adaptor) Name() string { return e.name }

// SetName sets the Adaptors name
func (e *Adaptor) SetName(n string) { e.name = n }

// Connect initializes the Joule for use with the Arduino beakout board
func (e *Adaptor) Connect() (err error) {
	e.digitalPins = make(map[int]sysfs.DigitalPin)
	e.pwmPins = make(map[int]*pwmPin)
	err = e.connect(e)
	return
}

// Finalize releases all i2c devices and exported digital and pwm pins.
func (e *Adaptor) Finalize() (err error) {
	for _, pin := range e.digitalPins {
		if pin != nil {
			if errs := pin.Unexport(); errs != nil {
				err = multierror.Append(err, errs)
			}
		}
	}
	for _, pin := range e.pwmPins {
		if pin != nil {
			if errs := pin.enable("0"); errs != nil {
				err = multierror.Append(err, errs)
			}
			if errs := pin.unexport(); errs != nil {
				err = multierror.Append(err, errs)
			}
		}
	}
	for _, bus := range e.i2cBuses {
		if bus != nil {
			if errs := bus.Close(); errs != nil {
				err = multierror.Append(err, errs)
			}
		}
	}
	return
}

// digitalPin returns matched digitalPin for specified values
func (e *Adaptor) digitalPin(pin string, dir string) (sysfsPin sysfs.DigitalPin, err error) {
	i := sysfsPinMap[pin]
	if e.digitalPins[i.pin] == nil {
		e.digitalPins[i.pin] = sysfs.NewDigitalPin(i.pin)
		if err = e.digitalPins[i.pin].Export(); err != nil {
			// TODO: log error
			return
		}
	}

	if dir == "in" {
		if err = e.digitalPins[i.pin].Direction(sysfs.IN); err != nil {
			return
		}
	} else if dir == "out" {
		if err = e.digitalPins[i.pin].Direction(sysfs.OUT); err != nil {
			return
		}
	}
	return e.digitalPins[i.pin], nil
}

// DigitalRead reads digital value from pin
func (e *Adaptor) DigitalRead(pin string) (i int, err error) {
	sysfsPin, err := e.digitalPin(pin, "in")
	if err != nil {
		return
	}
	return sysfsPin.Read()
}

// DigitalWrite writes a value to the pin. Acceptable values are 1 or 0.
func (e *Adaptor) DigitalWrite(pin string, val byte) (err error) {
	sysfsPin, err := e.digitalPin(pin, "out")
	if err != nil {
		return
	}
	return sysfsPin.Write(int(val))
}

// PwmWrite writes the 0-254 value to the specified pin
func (e *Adaptor) PwmWrite(pin string, val byte) (err error) {
	sysPin := sysfsPinMap[pin]
	if sysPin.pwmPin != -1 {
		if e.pwmPins[sysPin.pwmPin] == nil {
			if err = e.DigitalWrite(pin, 1); err != nil {
				return
			}
			e.pwmPins[sysPin.pwmPin] = newPwmPin(sysPin.pwmPin)
			if err = e.pwmPins[sysPin.pwmPin].export(); err != nil {
				return
			}
			if err = e.pwmPins[sysPin.pwmPin].enable("1"); err != nil {
				return
			}
		}
		p, err := e.pwmPins[sysPin.pwmPin].period()
		if err != nil {
			return err
		}
		period, err := strconv.Atoi(p)
		if err != nil {
			return err
		}
		duty := gobot.FromScale(float64(val), 0, 255.0)
		return e.pwmPins[sysPin.pwmPin].writeDuty(strconv.Itoa(int(float64(period) * duty)))
	}
	return errors.New("Not a PWM pin")
}

// I2cGetConnection returns a connection to a device on a specified bus.
// Valid bus number is [0..2] which corresponds to /dev/i2c-0 through /dev/i2c-2.
func (c *Adaptor) I2cGetConnection(address int, bus int) (connection i2c.I2cConnection, err error) {
	if (bus < 0) || (bus > 2) {
		return nil, fmt.Errorf("Bus number %d out of range", bus)
	}
	if c.i2cBuses[bus] == nil {
		c.i2cBuses[bus], err = sysfs.NewI2cDevice(fmt.Sprintf("/dev/i2c-%d", bus))
	}
	return i2c.NewI2cConnection(c.i2cBuses[bus], address), err
}

func (c *Adaptor) I2cGetDefaultBus() int {
	return 0
}
<|endoftext|>


<commit_before>// Copyright 2017 Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package e2e

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"path/filepath"
	"testing"
	"time"

	"istio.io/istio/pkg/log"
	"istio.io/istio/tests/e2e/framework"
	"istio.io/istio/tests/util"
)

type user struct {
	username      string
	sessionCookie string
}

type userVersion struct {
	user    user
	version string
	model   string
}

type versionRoutingRule struct {
	key          string
	userVersions []userVersion
}

func TestVersionRouting(t *testing.T) {
	v1Model := util.GetResourcePath(filepath.Join(modelDir, "productpage-normal-user-v1.html"))
	v2TestModel := util.GetResourcePath(filepath.Join(modelDir, "productpage-test-user-v2.html"))

	var rules = []versionRoutingRule{
		{key: testRule,
			userVersions: []userVersion{
				{
					user:    u1,
					version: "v1",
					model:   v1Model,
				},
				{
					user:    u2,
					version: "v2",
					model:   v2TestModel,
				},
			},
		},
	}

	testVersionRoutingRules(t, "v1alpha3", rules)
}

func testVersionRoutingRules(t *testing.T, configVersion string, rules []versionRoutingRule) {
	for _, rule := range rules {
		testVersionRoutingRule(t, configVersion, rule)
	}
}

func testVersionRoutingRule(t *testing.T, configVersion string, rule versionRoutingRule) {
	inspect(applyRules(configVersion, []string{rule.key}), "failed to apply rules", "", t)
	defer func() {
		inspect(deleteRules(configVersion, []string{rule.key}),
			fmt.Sprintf("failed to delete rules"), "", t)
		inspect(applyRules(configVersion, defaultRules), "failed to apply rules", "", t)
	}()

	for _, userVersion := range rule.userVersions {
		_, err := checkRoutingResponse(userVersion.user, userVersion.version,
			getIngressOrFail(t, configVersion), userVersion.model)
		inspect(
			err, fmt.Sprintf("Failed version routing! %s in %s", userVersion.user, userVersion.version),
			fmt.Sprintf("Success! Response matches with expected! %s in %s", userVersion.user,
				userVersion.version), t)
	}
}

func TestFaultDelay(t *testing.T) {
	var rules = []string{testRule, delayRule}
	doTestFaultDelay(t, "v1alpha3", rules)
}

func doTestFaultDelay(t *testing.T, configVersion string, rules []string) {
	inspect(applyRules(configVersion, rules), "failed to apply rules", "", t)
	defer func() {
		inspect(deleteRules(configVersion, rules), "failed to delete rules", "", t)
		inspect(applyRules(configVersion, defaultRules), "failed to apply rules", "", t)
	}()
	minDuration := 5
	maxDuration := 8
	standby := 10
	testModel := util.GetResourcePath(
		filepath.Join(modelDir, "productpage-test-user-v1-review-timeout.html"))
	for i := 0; i < testRetryTimes; i++ {
		duration, err := checkRoutingResponse(
			u2, "v1-timeout", getIngressOrFail(t, configVersion),
			testModel)
		log.Infof("Get response in %d second", duration)
		if err == nil && duration >= minDuration && duration <= maxDuration {
			log.Info("Success! Fault delay as expected")
			break
		}

		if i == testRetryTimes-1 {
			t.Errorf("Fault delay failed! Delay in %ds while expected between %ds and %ds, %s",
				duration, minDuration, maxDuration, err)
			break
		}

		log.Infof("Unexpected response, retry in %ds", standby)
		time.Sleep(time.Duration(standby) * time.Second)
	}
}

type migrationRule struct {
	key            string
	rate           float64
	modelToMigrate string
}

func TestVersionMigration(t *testing.T) {
	doTestVersionMigration(t, "v1alpha3")
}

func doTestVersionMigration(t *testing.T, configVersion string) {
	modelV2 := util.GetResourcePath(filepath.Join(modelDir, "productpage-normal-user-v2.html"))
	modelV3 := util.GetResourcePath(filepath.Join(modelDir, "productpage-normal-user-v3.html"))

	var rules = []migrationRule{
		{
			key:            fiftyRule,
			modelToMigrate: modelV3,
			rate:           0.5,
		},
		{
			key:            twentyRule,
			modelToMigrate: modelV2,
			rate:           0.2,
		},
		{
			key:            tenRule,
			modelToMigrate: modelV2,
			rate:           0.1,
		},
	}

	for _, rule := range rules {
		testVersionMigrationRule(t, configVersion, rule)
	}
}

func testVersionMigrationRule(t *testing.T, configVersion string, rule migrationRule) {
	inspect(applyRules(configVersion, []string{rule.key}), "failed to apply rules", "", t)
	defer func() {
		inspect(deleteRules(configVersion, []string{rule.key}),
			fmt.Sprintf("failed to delete rules"), "", t)
		inspect(applyRules(configVersion, defaultRules), "failed to apply rules", "", t)
	}()
	modelV1 := util.GetResourcePath(filepath.Join(modelDir, "productpage-normal-user-v1.html"))
	tolerance := 0.05
	totalShot := 100
	cookies := []http.Cookie{
		{
			Name:  "foo",
			Value: "bar",
		},
		{
			Name:  "session",
			Value: u1.sessionCookie,
		},
	}

	for i := 0; i < testRetryTimes; i++ {
		c1, cVersionToMigrate := 0, 0
		for c := 0; c < totalShot; c++ {
			resp, err := getWithCookie(fmt.Sprintf("%s/productpage",
				getIngressOrFail(t, configVersion)), cookies)
			inspect(err, "Failed to record", "", t)
			if resp.StatusCode != http.StatusOK {
				log.Errorf("unexpected response status %d", resp.StatusCode)
				continue
			}
			body, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				log.Errora(err)
				continue
			}

			var c1CompareError, cVersionToMigrateError error
			if c1CompareError = util.CompareToFile(body, modelV1); c1CompareError == nil {
				c1++
			} else if cVersionToMigrateError = util.CompareToFile(body, rule.modelToMigrate); cVersionToMigrateError == nil {
				cVersionToMigrate++
			} else {
				log.Error("received unexpected version: %s")
				log.Infof("comparing to the original version: %v", c1CompareError)
				log.Infof("comparing to the version to migrate to: %v", cVersionToMigrateError)
			}
			closeResponseBody(resp)
		}

		if isWithinPercentage(c1, totalShot, 1.0-rule.rate, tolerance) &&
			isWithinPercentage(cVersionToMigrate, totalShot, rule.rate, tolerance) {
			log.Infof(
				"Success! Version migration acts as expected for rate %f, "+
					"old version hit %d, new version hit %d", rule.rate, c1, cVersionToMigrate)
			break
		}

		if i == testRetryTimes-1 {
			t.Errorf("Failed version migration test for rate %f, "+
				"old version hit %d, new version hit %d", rule.rate, c1, cVersionToMigrate)
		}
	}
}

func isWithinPercentage(count int, total int, rate float64, tolerance float64) bool {
	minimum := int((rate - tolerance) * float64(total))
	maximum := int((rate + tolerance) * float64(total))
	return count >= minimum && count <= maximum
}

func TestDbRoutingMongo(t *testing.T) {
	var rules = []string{testDbRule}
	doTestDbRoutingMongo(t, "v1alpha3", rules)
}

func doTestDbRoutingMongo(t *testing.T, configVersion string, rules []string) {
	var err error
	inspect(applyRules(configVersion, rules), "failed to apply rules", "", t)
	defer func() {
		inspect(deleteRules(configVersion, rules), "failed to delete rules", "", t)
		inspect(applyRules(configVersion, defaultRules), "failed to apply rules", "", t)
	}()

	// TODO: update the rating in the db and check the value on page

	respExpr := "glyphicon-star" // not great test for v2 or v3 being alive

	_, err = checkHTTPResponse(getIngressOrFail(t, configVersion), respExpr, 10)
	inspect(
		err, fmt.Sprintf("Failed database routing! %s in v1", u1),
		fmt.Sprintf("Success! Response matches with expected! %s", respExpr), t)
}

func TestDbRoutingMysql(t *testing.T) {
	var rules = []string{testMysqlRule}

	doTestDbRoutingMysql(t, "v1alpha3", rules)
}

func doTestDbRoutingMysql(t *testing.T, configVersion string, rules []string) {
	var err error
	inspect(applyRules(configVersion, rules), "failed to apply rules", "", t)
	defer func() {
		inspect(deleteRules(configVersion, rules), "failed to delete rules", "", t)
		inspect(applyRules(configVersion, defaultRules), "failed to apply rules", "", t)
	}()

	// TODO: update the rating in the db and check the value on page

	respExpr := "glyphicon-star" // not great test for v2 or v3 being alive

	_, err = checkHTTPResponse(getIngressOrFail(t, configVersion), respExpr, 10)
	inspect(
		err, fmt.Sprintf("Failed database routing! %s in v1", u1),
		fmt.Sprintf("Success! Response matches with expected! %s", respExpr), t)
}

func TestVMExtendsIstio(t *testing.T) {
	t.Skip("issue https://github.com/istio/istio/issues/4794")
	if *framework.TestVM {
		// TODO (chx) vm_provider flag to select venders
		vm, err := framework.NewGCPRawVM(tc.CommonConfig.Kube.Namespace)
		inspect(err, "unable to configure VM", "VM configured correctly", t)
		// VM setup and teardown is manual for now
		// will be replaced with preprovision server calls
		err = vm.Setup()
		inspect(err, "VM setup failed", "VM setup succeeded", t)
		_, err = vm.SecureShell("curl -v istio-pilot:8080")
		inspect(err, "VM failed to extend istio", "VM extends istio service mesh", t)
		_, err2 := vm.SecureShell(fmt.Sprintf(
			"host istio-pilot.%s.svc.cluster.local.", vm.Namespace))
		inspect(err2, "VM failed to extend istio", "VM extends istio service mesh", t)
		err = vm.Teardown()
		inspect(err, "VM teardown failed", "VM teardown succeeded", t)
	}
}

func TestExternalDetailsService(t *testing.T) {
	if !tf.Egress {
		t.Skipf("Skipping %s: egress=false", t.Name())
	}

	var rules = []string{detailsExternalServiceRouteRule, detailsExternalServiceEgressRule}

	doTestExternalDetailsService(t, "v1alpha3", rules)
}

func doTestExternalDetailsService(t *testing.T, configVersion string, rules []string) {
	var err error
	inspect(applyRules(configVersion, rules), "failed to apply rules", "", t)
	defer func() {
		inspect(deleteRules(configVersion, rules), "failed to delete rules", "", t)
		inspect(applyRules(configVersion, defaultRules), "failed to apply rules", "", t)
	}()

	isbnFetchedFromExternalService := "0486424618"

	_, err = checkHTTPResponse(getIngressOrFail(t, configVersion), isbnFetchedFromExternalService, 1)
	inspect(
		err, fmt.Sprintf("Failed external details routing! %s in v1", u1),
		fmt.Sprintf("Success! Response matches with expected! %s", isbnFetchedFromExternalService), t)
}
<commit_msg>[e2e] fix wrong output format (#8206)<commit_after>// Copyright 2017 Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package e2e

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"path/filepath"
	"testing"
	"time"

	"istio.io/istio/pkg/log"
	"istio.io/istio/tests/e2e/framework"
	"istio.io/istio/tests/util"
)

type user struct {
	username      string
	sessionCookie string
}

type userVersion struct {
	user    user
	version string
	model   string
}

type versionRoutingRule struct {
	key          string
	userVersions []userVersion
}

func TestVersionRouting(t *testing.T) {
	v1Model := util.GetResourcePath(filepath.Join(modelDir, "productpage-normal-user-v1.html"))
	v2TestModel := util.GetResourcePath(filepath.Join(modelDir, "productpage-test-user-v2.html"))

	var rules = []versionRoutingRule{
		{key: testRule,
			userVersions: []userVersion{
				{
					user:    u1,
					version: "v1",
					model:   v1Model,
				},
				{
					user:    u2,
					version: "v2",
					model:   v2TestModel,
				},
			},
		},
	}

	testVersionRoutingRules(t, "v1alpha3", rules)
}

func testVersionRoutingRules(t *testing.T, configVersion string, rules []versionRoutingRule) {
	for _, rule := range rules {
		testVersionRoutingRule(t, configVersion, rule)
	}
}

func testVersionRoutingRule(t *testing.T, configVersion string, rule versionRoutingRule) {
	inspect(applyRules(configVersion, []string{rule.key}), "failed to apply rules", "", t)
	defer func() {
		inspect(deleteRules(configVersion, []string{rule.key}),
			fmt.Sprintf("failed to delete rules"), "", t)
		inspect(applyRules(configVersion, defaultRules), "failed to apply rules", "", t)
	}()

	for _, userVersion := range rule.userVersions {
		_, err := checkRoutingResponse(userVersion.user, userVersion.version,
			getIngressOrFail(t, configVersion), userVersion.model)
		inspect(
			err, fmt.Sprintf("Failed version routing! %s in %s", userVersion.user, userVersion.version),
			fmt.Sprintf("Success! Response matches with expected! %s in %s", userVersion.user,
				userVersion.version), t)
	}
}

func TestFaultDelay(t *testing.T) {
	var rules = []string{testRule, delayRule}
	doTestFaultDelay(t, "v1alpha3", rules)
}

func doTestFaultDelay(t *testing.T, configVersion string, rules []string) {
	inspect(applyRules(configVersion, rules), "failed to apply rules", "", t)
	defer func() {
		inspect(deleteRules(configVersion, rules), "failed to delete rules", "", t)
		inspect(applyRules(configVersion, defaultRules), "failed to apply rules", "", t)
	}()
	minDuration := 5
	maxDuration := 8
	standby := 10
	testModel := util.GetResourcePath(
		filepath.Join(modelDir, "productpage-test-user-v1-review-timeout.html"))
	for i := 0; i < testRetryTimes; i++ {
		duration, err := checkRoutingResponse(
			u2, "v1-timeout", getIngressOrFail(t, configVersion),
			testModel)
		log.Infof("Get response in %d second", duration)
		if err == nil && duration >= minDuration && duration <= maxDuration {
			log.Info("Success! Fault delay as expected")
			break
		}

		if i == testRetryTimes-1 {
			t.Errorf("Fault delay failed! Delay in %ds while expected between %ds and %ds, %s",
				duration, minDuration, maxDuration, err)
			break
		}

		log.Infof("Unexpected response, retry in %ds", standby)
		time.Sleep(time.Duration(standby) * time.Second)
	}
}

type migrationRule struct {
	key            string
	rate           float64
	modelToMigrate string
}

func TestVersionMigration(t *testing.T) {
	doTestVersionMigration(t, "v1alpha3")
}

func doTestVersionMigration(t *testing.T, configVersion string) {
	modelV2 := util.GetResourcePath(filepath.Join(modelDir, "productpage-normal-user-v2.html"))
	modelV3 := util.GetResourcePath(filepath.Join(modelDir, "productpage-normal-user-v3.html"))

	var rules = []migrationRule{
		{
			key:            fiftyRule,
			modelToMigrate: modelV3,
			rate:           0.5,
		},
		{
			key:            twentyRule,
			modelToMigrate: modelV2,
			rate:           0.2,
		},
		{
			key:            tenRule,
			modelToMigrate: modelV2,
			rate:           0.1,
		},
	}

	for _, rule := range rules {
		testVersionMigrationRule(t, configVersion, rule)
	}
}

func testVersionMigrationRule(t *testing.T, configVersion string, rule migrationRule) {
	inspect(applyRules(configVersion, []string{rule.key}), "failed to apply rules", "", t)
	defer func() {
		inspect(deleteRules(configVersion, []string{rule.key}),
			fmt.Sprintf("failed to delete rules"), "", t)
		inspect(applyRules(configVersion, defaultRules), "failed to apply rules", "", t)
	}()
	modelV1 := util.GetResourcePath(filepath.Join(modelDir, "productpage-normal-user-v1.html"))
	tolerance := 0.05
	totalShot := 100
	cookies := []http.Cookie{
		{
			Name:  "foo",
			Value: "bar",
		},
		{
			Name:  "session",
			Value: u1.sessionCookie,
		},
	}

	for i := 0; i < testRetryTimes; i++ {
		c1, cVersionToMigrate := 0, 0
		for c := 0; c < totalShot; c++ {
			resp, err := getWithCookie(fmt.Sprintf("%s/productpage",
				getIngressOrFail(t, configVersion)), cookies)
			inspect(err, "Failed to record", "", t)
			if resp.StatusCode != http.StatusOK {
				log.Errorf("unexpected response status %d", resp.StatusCode)
				continue
			}
			body, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				log.Errora(err)
				continue
			}

			var c1CompareError, cVersionToMigrateError error
			if c1CompareError = util.CompareToFile(body, modelV1); c1CompareError == nil {
				c1++
			} else if cVersionToMigrateError = util.CompareToFile(body, rule.modelToMigrate); cVersionToMigrateError == nil {
				cVersionToMigrate++
			} else {
				log.Errorf("received unexpected version: %s", configVersion)
				log.Infof("comparing to the original version: %v", c1CompareError)
				log.Infof("comparing to the version to migrate to: %v", cVersionToMigrateError)
			}
			closeResponseBody(resp)
		}

		if isWithinPercentage(c1, totalShot, 1.0-rule.rate, tolerance) &&
			isWithinPercentage(cVersionToMigrate, totalShot, rule.rate, tolerance) {
			log.Infof(
				"Success! Version migration acts as expected for rate %f, "+
					"old version hit %d, new version hit %d", rule.rate, c1, cVersionToMigrate)
			break
		}

		if i == testRetryTimes-1 {
			t.Errorf("Failed version migration test for rate %f, "+
				"old version hit %d, new version hit %d", rule.rate, c1, cVersionToMigrate)
		}
	}
}

func isWithinPercentage(count int, total int, rate float64, tolerance float64) bool {
	minimum := int((rate - tolerance) * float64(total))
	maximum := int((rate + tolerance) * float64(total))
	return count >= minimum && count <= maximum
}

func TestDbRoutingMongo(t *testing.T) {
	var rules = []string{testDbRule}
	doTestDbRoutingMongo(t, "v1alpha3", rules)
}

func doTestDbRoutingMongo(t *testing.T, configVersion string, rules []string) {
	var err error
	inspect(applyRules(configVersion, rules), "failed to apply rules", "", t)
	defer func() {
		inspect(deleteRules(configVersion, rules), "failed to delete rules", "", t)
		inspect(applyRules(configVersion, defaultRules), "failed to apply rules", "", t)
	}()

	// TODO: update the rating in the db and check the value on page

	respExpr := "glyphicon-star" // not great test for v2 or v3 being alive

	_, err = checkHTTPResponse(getIngressOrFail(t, configVersion), respExpr, 10)
	inspect(
		err, fmt.Sprintf("Failed database routing! %s in v1", u1),
		fmt.Sprintf("Success! Response matches with expected! %s", respExpr), t)
}

func TestDbRoutingMysql(t *testing.T) {
	var rules = []string{testMysqlRule}

	doTestDbRoutingMysql(t, "v1alpha3", rules)
}

func doTestDbRoutingMysql(t *testing.T, configVersion string, rules []string) {
	var err error
	inspect(applyRules(configVersion, rules), "failed to apply rules", "", t)
	defer func() {
		inspect(deleteRules(configVersion, rules), "failed to delete rules", "", t)
		inspect(applyRules(configVersion, defaultRules), "failed to apply rules", "", t)
	}()

	// TODO: update the rating in the db and check the value on page

	respExpr := "glyphicon-star" // not great test for v2 or v3 being alive

	_, err = checkHTTPResponse(getIngressOrFail(t, configVersion), respExpr, 10)
	inspect(
		err, fmt.Sprintf("Failed database routing! %s in v1", u1),
		fmt.Sprintf("Success! Response matches with expected! %s", respExpr), t)
}

func TestVMExtendsIstio(t *testing.T) {
	t.Skip("issue https://github.com/istio/istio/issues/4794")
	if *framework.TestVM {
		// TODO (chx) vm_provider flag to select venders
		vm, err := framework.NewGCPRawVM(tc.CommonConfig.Kube.Namespace)
		inspect(err, "unable to configure VM", "VM configured correctly", t)
		// VM setup and teardown is manual for now
		// will be replaced with preprovision server calls
		err = vm.Setup()
		inspect(err, "VM setup failed", "VM setup succeeded", t)
		_, err = vm.SecureShell("curl -v istio-pilot:8080")
		inspect(err, "VM failed to extend istio", "VM extends istio service mesh", t)
		_, err2 := vm.SecureShell(fmt.Sprintf(
			"host istio-pilot.%s.svc.cluster.local.", vm.Namespace))
		inspect(err2, "VM failed to extend istio", "VM extends istio service mesh", t)
		err = vm.Teardown()
		inspect(err, "VM teardown failed", "VM teardown succeeded", t)
	}
}

func TestExternalDetailsService(t *testing.T) {
	if !tf.Egress {
		t.Skipf("Skipping %s: egress=false", t.Name())
	}

	var rules = []string{detailsExternalServiceRouteRule, detailsExternalServiceEgressRule}

	doTestExternalDetailsService(t, "v1alpha3", rules)
}

func doTestExternalDetailsService(t *testing.T, configVersion string, rules []string) {
	var err error
	inspect(applyRules(configVersion, rules), "failed to apply rules", "", t)
	defer func() {
		inspect(deleteRules(configVersion, rules), "failed to delete rules", "", t)
		inspect(applyRules(configVersion, defaultRules), "failed to apply rules", "", t)
	}()

	isbnFetchedFromExternalService := "0486424618"

	_, err = checkHTTPResponse(getIngressOrFail(t, configVersion), isbnFetchedFromExternalService, 1)
	inspect(
		err, fmt.Sprintf("Failed external details routing! %s in v1", u1),
		fmt.Sprintf("Success! Response matches with expected! %s", isbnFetchedFromExternalService), t)
}
<|endoftext|>