<commit_before>package eval

// Builtin functions.

import (
	"bufio"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"os"
	"os/user"
	"reflect"
	"runtime"
	"strconv"
)

var builtinFns []*BuiltinFn

func init() {
	// Needed to work around init loop.
	builtinFns = []*BuiltinFn{
		&BuiltinFn{":", nop},
		&BuiltinFn{"true", nop},

		&BuiltinFn{"print", wrapFn(print)},
		&BuiltinFn{"println", wrapFn(println)},

		&BuiltinFn{"into-lines", wrapFn(intoLines)},
		&BuiltinFn{"from-lines", wrapFn(fromLines)},

		&BuiltinFn{"rat", wrapFn(ratFn)},

		&BuiltinFn{"put", put},
		&BuiltinFn{"put-all", wrapFn(putAll)},
		&BuiltinFn{"unpack", wrapFn(unpack)},

		&BuiltinFn{"from-json", wrapFn(fromJSON)},

		&BuiltinFn{"typeof", typeof},

		&BuiltinFn{"fail", wrapFn(fail)},
		&BuiltinFn{"return", wrapFn(returnFn)},
		&BuiltinFn{"break", wrapFn(breakFn)},
		&BuiltinFn{"continue", wrapFn(continueFn)},

		&BuiltinFn{"each", wrapFn(each)},

		&BuiltinFn{"cd", cd},
		&BuiltinFn{"visited-dirs", wrapFn(visistedDirs)},
		&BuiltinFn{"jump-dir", wrapFn(jumpDir)},

		&BuiltinFn{"source", wrapFn(source)},

		&BuiltinFn{"+", wrapFn(plus)},
		&BuiltinFn{"-", wrapFn(minus)},
		&BuiltinFn{"*", wrapFn(times)},
		&BuiltinFn{"/", wrapFn(divide)},

		&BuiltinFn{"=", eq},
		&BuiltinFn{"deepeq", deepeq},

		&BuiltinFn{"bind", wrapFn(bind)},
		&BuiltinFn{"le", wrapFn(le)},

		&BuiltinFn{"-stack", wrapFn(_stack)},
	}
}

var (
	argsError         = errors.New("args error")
	inputError        = errors.New("input error")
	storeNotConnected = errors.New("store not connected")
	noMatchingDir     = errors.New("no matching directory")
	noEditor          = errors.New("no line editor")
)

var (
	evalCtxType = reflect.TypeOf((*evalCtx)(nil))
	valueType   = reflect.TypeOf((*Value)(nil)).Elem()
)

// wrapFn wraps an inner function into one suitable as a builtin function. It
// generates argument checking and conversion code according to the signature
// of the inner function. The inner function must accept evalCtx* as the first
// argument and return an exitus.
func wrapFn(inner interface{}) func(*evalCtx, []Value) {
	type_ := reflect.TypeOf(inner)
	if type_.In(0) != evalCtxType {
		panic("bad func")
	}

	requiredArgs := type_.NumIn() - 1
	isVariadic := type_.IsVariadic()
	var variadicType reflect.Type
	if isVariadic {
		requiredArgs -= 1
		variadicType = type_.In(type_.NumIn() - 1).Elem()
		if !supportedIn(variadicType) {
			panic("bad func argument")
		}
	}

	for i := 0; i < requiredArgs; i++ {
		if !supportedIn(type_.In(i + 1)) {
			panic("bad func argument")
		}
	}

	return func(ec *evalCtx, args []Value) {
		if len(args) < requiredArgs || (!isVariadic && len(args) > requiredArgs) {
			throw(argsError)
		}
		callArgs := make([]reflect.Value, len(args)+1)
		callArgs[0] = reflect.ValueOf(ec)

		ok := convertArgs(args[:requiredArgs], callArgs[1:],
			func(i int) reflect.Type { return type_.In(i + 1) })
		if !ok {
			throw(argsError)
		}
		if isVariadic {
			ok := convertArgs(args[requiredArgs:], callArgs[1+requiredArgs:],
				func(i int) reflect.Type { return variadicType })
			if !ok {
				throw(argsError)
			}
		}
		reflect.ValueOf(inner).Call(callArgs)
	}
}

func supportedIn(t reflect.Type) bool {
	return t.Kind() == reflect.String || t.Kind() == reflect.Float64 ||
		t.Implements(valueType)
}

func convertArgs(args []Value, callArgs []reflect.Value, callType func(int) reflect.Type) bool {
	for i, arg := range args {
		var callArg interface{}
		switch callType(i).Kind() {
		case reflect.String:
			callArg = ToString(arg)
		case reflect.Float64:
			var err error
			callArg, err = toFloat(arg)
			if err != nil {
				return false
				// return err
			}
		default:
			if reflect.TypeOf(arg).ConvertibleTo(callType(i)) {
				callArg = arg
			} else {
				return false
				// return argsError
			}
		}
		callArgs[i] = reflect.ValueOf(callArg)
	}
	return true
}

func nop(ec *evalCtx, args []Value) {
}

func put(ec *evalCtx, args []Value) {
	out := ec.ports[1].ch
	for _, a := range args {
		out <- a
	}
}

func putAll(ec *evalCtx, lists ...*List) {
	out := ec.ports[1].ch
	for _, list := range lists {
		for _, x := range *list.inner {
			out <- x
		}
	}
}

func typeof(ec *evalCtx, args []Value) {
	out := ec.ports[1].ch
	for _, a := range args {
		out <- String(a.Type().String())
	}
}

func fail(ec *evalCtx, arg Value) {
	throw(errors.New(ToString(arg)))
}

func returnFn(ec *evalCtx) {
	throw(Return)
}

func breakFn(ec *evalCtx) {
	throw(Break)
}

func continueFn(ec *evalCtx) {
	throw(Continue)
}

func print(ec *evalCtx, args ...string) {
	out := ec.ports[1].f
	for i, arg := range args {
		if i > 0 {
			out.WriteString(" ")
		}
		out.WriteString(arg)
	}
}

func println(ec *evalCtx, args ...string) {
	print(ec, args...)
	ec.ports[1].f.WriteString("\n")
}

func intoLines(ec *evalCtx) {
	in := ec.ports[0].ch
	out := ec.ports[1].f

	for v := range in {
		fmt.Fprintln(out, ToString(v))
	}
}

func fromLines(ec *evalCtx) {
	in := ec.ports[0].f
	out := ec.ports[1].ch

	bufferedIn := bufio.NewReader(in)
	for {
		line, err := bufferedIn.ReadString('\n')
		if err == io.EOF {
			return
		} else if err != nil {
			throw(err)
		}
		out <- String(line[:len(line)-1])
	}
}

func ratFn(ec *evalCtx, arg Value) {
	out := ec.ports[1].ch
	r, err := ToRat(arg)
	if err != nil {
		throw(err)
	}
	out <- r
}

// unpack takes any number of tables and output their list elements.
func unpack(ec *evalCtx) {
	in := ec.ports[0].ch
	out := ec.ports[1].ch

	for v := range in {
		if list, ok := v.(*List); !ok {
			throw(inputError)
		} else {
			for _, e := range *list.inner {
				out <- e
			}
		}
	}
}

// fromJSON parses a stream of JSON data into Value's.
func fromJSON(ec *evalCtx) {
	in := ec.ports[0].f
	out := ec.ports[1].ch

	dec := json.NewDecoder(in)
	var v interface{}
	for {
		err := dec.Decode(&v)
		if err != nil {
			if err == io.EOF {
				return
			}
			throw(err)
		}
		out <- FromJSONInterface(v)
	}
}

// each takes a single closure and applies it to all input values.
func each(ec *evalCtx, f *Closure) {
	in := ec.ports[0].ch
in:
	for v := range in {
		newec := ec.fork("closure of each")
		ex := newec.pcall(f, []Value{v})
		newec.closePorts()

		switch ex {
		case nil, Continue:
			// nop
		case Break:
			break in
		default:
			throw(ex)
		}
	}
}

func cd(ec *evalCtx, args []Value) {
	var dir string
	if len(args) == 0 {
		user, err := user.Current()
		if err == nil {
			dir = user.HomeDir
		} else {
			throw(errors.New("cannot get current user: " + err.Error()))
		}
	} else if len(args) == 1 {
		dir = ToString(args[0])
	} else {
		throw(argsError)
	}

	cdInner(dir, ec)
}

func cdInner(dir string, ec *evalCtx) {
	err := os.Chdir(dir)
	if err != nil {
		throw(err)
	}
	if ec.store != nil {
		pwd, err := os.Getwd()
		// BUG(xiaq): Possible error of os.Getwd after cd-ing is ignored.
		if err == nil {
			ec.store.AddDir(pwd)
		}
	}
}

func visistedDirs(ec *evalCtx) {
	if ec.store == nil {
		throw(storeNotConnected)
	}
	dirs, err := ec.store.ListDirs()
	if err != nil {
		throw(errors.New("store error: " + err.Error()))
	}
	out := ec.ports[1].ch
	for _, dir := range dirs {
		out <- Map{&map[Value]Value{
			String("path"):  String(dir.Path),
			String("score"): String(fmt.Sprint(dir.Score)),
		}}
	}
}

func jumpDir(ec *evalCtx, arg string) {
	if ec.store == nil {
		throw(storeNotConnected)
	}
	dirs, err := ec.store.FindDirs(arg)
	if err != nil {
		throw(errors.New("store error: " + err.Error()))
	}
	if len(dirs) == 0 {
		throw(noMatchingDir)
	}
	dir := dirs[0].Path
	err = os.Chdir(dir)
	// TODO(xiaq): Remove directories that no longer exist
	if err != nil {
		throw(err)
	}
	ec.store.AddDir(dir)
}

func source(ec *evalCtx, fname string) {
	ec.Source(fname)
}

func toFloat(arg Value) (float64, error) {
	arg, ok := arg.(String)
	if !ok {
		return 0, fmt.Errorf("must be string")
	}
	num, err := strconv.ParseFloat(string(arg.(String)), 64)
	if err != nil {
		return 0, err
	}
	return num, nil
}

func plus(ec *evalCtx, nums ...float64) {
	out := ec.ports[1].ch
	sum := 0.0
	for _, f := range nums {
		sum += f
	}
	out <- String(fmt.Sprintf("%g", sum))
}

func minus(ec *evalCtx, sum float64, nums ...float64) {
	out := ec.ports[1].ch
	for _, f := range nums {
		sum -= f
	}
	out <- String(fmt.Sprintf("%g", sum))
}

func times(ec *evalCtx, nums ...float64) {
	out := ec.ports[1].ch
	prod := 1.0
	for _, f := range nums {
		prod *= f
	}
	out <- String(fmt.Sprintf("%g", prod))
}

func divide(ec *evalCtx, prod float64, nums ...float64) {
	out := ec.ports[1].ch
	for _, f := range nums {
		prod /= f
	}
	out <- String(fmt.Sprintf("%g", prod))
}

func eq(ec *evalCtx, args []Value) {
	out := ec.ports[1].ch
	if len(args) == 0 {
		throw(argsError)
	}
	for i := 0; i+1 < len(args); i++ {
		if args[i] != args[i+1] {
			out <- Bool(false)
			return
		}
	}
	out <- Bool(true)
}

func deepeq(ec *evalCtx, args []Value) {
	out := ec.ports[1].ch
	if len(args) == 0 {
		throw(argsError)
	}
	for i := 0; i+1 < len(args); i++ {
		if !DeepEq(args[i], args[i+1]) {
			out <- Bool(false)
			return
		}
	}
	out <- Bool(true)
}

func bind(ec *evalCtx, key string, function string) {
	if ec.Editor == nil {
		throw(noEditor)
	}
	maybeThrow(ec.Editor.Bind(key, String(function)))
}

func le(ec *evalCtx, name string, args ...Value) {
	if ec.Editor == nil {
		throw(noEditor)
	}
	maybeThrow(ec.Editor.Call(name, args))
}

func _stack(ec *evalCtx) {
	out := ec.ports[1].f

	// XXX dup with main.go
	buf := make([]byte, 1024)
	for runtime.Stack(buf, true) == cap(buf) {
		buf = make([]byte, cap(buf)*2)
	}
	out.Write(buf)
}
<commit_msg>eval: fix unpack and put-all to use List instead of *List<commit_after>package eval

// Builtin functions.

import (
	"bufio"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"os"
	"os/user"
	"reflect"
	"runtime"
	"strconv"
)

var builtinFns []*BuiltinFn

func init() {
	// Needed to work around init loop.
	builtinFns = []*BuiltinFn{
		&BuiltinFn{":", nop},
		&BuiltinFn{"true", nop},

		&BuiltinFn{"print", wrapFn(print)},
		&BuiltinFn{"println", wrapFn(println)},

		&BuiltinFn{"into-lines", wrapFn(intoLines)},
		&BuiltinFn{"from-lines", wrapFn(fromLines)},

		&BuiltinFn{"rat", wrapFn(ratFn)},

		&BuiltinFn{"put", put},
		&BuiltinFn{"put-all", wrapFn(putAll)},
		&BuiltinFn{"unpack", wrapFn(unpack)},

		&BuiltinFn{"from-json", wrapFn(fromJSON)},

		&BuiltinFn{"typeof", typeof},

		&BuiltinFn{"fail", wrapFn(fail)},
		&BuiltinFn{"return", wrapFn(returnFn)},
		&BuiltinFn{"break", wrapFn(breakFn)},
		&BuiltinFn{"continue", wrapFn(continueFn)},

		&BuiltinFn{"each", wrapFn(each)},

		&BuiltinFn{"cd", cd},
		&BuiltinFn{"visited-dirs", wrapFn(visistedDirs)},
		&BuiltinFn{"jump-dir", wrapFn(jumpDir)},

		&BuiltinFn{"source", wrapFn(source)},

		&BuiltinFn{"+", wrapFn(plus)},
		&BuiltinFn{"-", wrapFn(minus)},
		&BuiltinFn{"*", wrapFn(times)},
		&BuiltinFn{"/", wrapFn(divide)},

		&BuiltinFn{"=", eq},
		&BuiltinFn{"deepeq", deepeq},

		&BuiltinFn{"bind", wrapFn(bind)},
		&BuiltinFn{"le", wrapFn(le)},

		&BuiltinFn{"-stack", wrapFn(_stack)},
	}
}

var (
	argsError         = errors.New("args error")
	inputError        = errors.New("input error")
	storeNotConnected = errors.New("store not connected")
	noMatchingDir     = errors.New("no matching directory")
	noEditor          = errors.New("no line editor")
)

var (
	evalCtxType = reflect.TypeOf((*evalCtx)(nil))
	valueType   = reflect.TypeOf((*Value)(nil)).Elem()
)

// wrapFn wraps an inner function into one suitable as a builtin function. It
// generates argument checking and conversion code according to the signature
// of the inner function. The inner function must accept evalCtx* as the first
// argument and return an exitus.
func wrapFn(inner interface{}) func(*evalCtx, []Value) {
	type_ := reflect.TypeOf(inner)
	if type_.In(0) != evalCtxType {
		panic("bad func")
	}

	requiredArgs := type_.NumIn() - 1
	isVariadic := type_.IsVariadic()
	var variadicType reflect.Type
	if isVariadic {
		requiredArgs -= 1
		variadicType = type_.In(type_.NumIn() - 1).Elem()
		if !supportedIn(variadicType) {
			panic("bad func argument")
		}
	}

	for i := 0; i < requiredArgs; i++ {
		if !supportedIn(type_.In(i + 1)) {
			panic("bad func argument")
		}
	}

	return func(ec *evalCtx, args []Value) {
		if len(args) < requiredArgs || (!isVariadic && len(args) > requiredArgs) {
			throw(argsError)
		}
		callArgs := make([]reflect.Value, len(args)+1)
		callArgs[0] = reflect.ValueOf(ec)

		ok := convertArgs(args[:requiredArgs], callArgs[1:],
			func(i int) reflect.Type { return type_.In(i + 1) })
		if !ok {
			throw(argsError)
		}
		if isVariadic {
			ok := convertArgs(args[requiredArgs:], callArgs[1+requiredArgs:],
				func(i int) reflect.Type { return variadicType })
			if !ok {
				throw(argsError)
			}
		}
		reflect.ValueOf(inner).Call(callArgs)
	}
}

func supportedIn(t reflect.Type) bool {
	return t.Kind() == reflect.String || t.Kind() == reflect.Float64 ||
		t.Implements(valueType)
}

func convertArgs(args []Value, callArgs []reflect.Value, callType func(int) reflect.Type) bool {
	for i, arg := range args {
		var callArg interface{}
		switch callType(i).Kind() {
		case reflect.String:
			callArg = ToString(arg)
		case reflect.Float64:
			var err error
			callArg, err = toFloat(arg)
			if err != nil {
				return false
				// return err
			}
		default:
			if reflect.TypeOf(arg).ConvertibleTo(callType(i)) {
				callArg = arg
			} else {
				return false
				// return argsError
			}
		}
		callArgs[i] = reflect.ValueOf(callArg)
	}
	return true
}

func nop(ec *evalCtx, args []Value) {
}

func put(ec *evalCtx, args []Value) {
	out := ec.ports[1].ch
	for _, a := range args {
		out <- a
	}
}

func putAll(ec *evalCtx, lists ...List) {
	out := ec.ports[1].ch
	for _, list := range lists {
		for _, x := range *list.inner {
			out <- x
		}
	}
}

func typeof(ec *evalCtx, args []Value) {
	out := ec.ports[1].ch
	for _, a := range args {
		out <- String(a.Type().String())
	}
}

func fail(ec *evalCtx, arg Value) {
	throw(errors.New(ToString(arg)))
}

func returnFn(ec *evalCtx) {
	throw(Return)
}

func breakFn(ec *evalCtx) {
	throw(Break)
}

func continueFn(ec *evalCtx) {
	throw(Continue)
}

func print(ec *evalCtx, args ...string) {
	out := ec.ports[1].f
	for i, arg := range args {
		if i > 0 {
			out.WriteString(" ")
		}
		out.WriteString(arg)
	}
}

func println(ec *evalCtx, args ...string) {
	print(ec, args...)
	ec.ports[1].f.WriteString("\n")
}

func intoLines(ec *evalCtx) {
	in := ec.ports[0].ch
	out := ec.ports[1].f

	for v := range in {
		fmt.Fprintln(out, ToString(v))
	}
}

func fromLines(ec *evalCtx) {
	in := ec.ports[0].f
	out := ec.ports[1].ch

	bufferedIn := bufio.NewReader(in)
	for {
		line, err := bufferedIn.ReadString('\n')
		if err == io.EOF {
			return
		} else if err != nil {
			throw(err)
		}
		out <- String(line[:len(line)-1])
	}
}

func ratFn(ec *evalCtx, arg Value) {
	out := ec.ports[1].ch
	r, err := ToRat(arg)
	if err != nil {
		throw(err)
	}
	out <- r
}

// unpack takes any number of tables and output their list elements.
func unpack(ec *evalCtx) {
	in := ec.ports[0].ch
	out := ec.ports[1].ch

	for v := range in {
		if list, ok := v.(List); !ok {
			throw(inputError)
		} else {
			for _, e := range *list.inner {
				out <- e
			}
		}
	}
}

// fromJSON parses a stream of JSON data into Value's.
func fromJSON(ec *evalCtx) {
	in := ec.ports[0].f
	out := ec.ports[1].ch

	dec := json.NewDecoder(in)
	var v interface{}
	for {
		err := dec.Decode(&v)
		if err != nil {
			if err == io.EOF {
				return
			}
			throw(err)
		}
		out <- FromJSONInterface(v)
	}
}

// each takes a single closure and applies it to all input values.
func each(ec *evalCtx, f *Closure) {
	in := ec.ports[0].ch
in:
	for v := range in {
		newec := ec.fork("closure of each")
		ex := newec.pcall(f, []Value{v})
		newec.closePorts()

		switch ex {
		case nil, Continue:
			// nop
		case Break:
			break in
		default:
			throw(ex)
		}
	}
}

func cd(ec *evalCtx, args []Value) {
	var dir string
	if len(args) == 0 {
		user, err := user.Current()
		if err == nil {
			dir = user.HomeDir
		} else {
			throw(errors.New("cannot get current user: " + err.Error()))
		}
	} else if len(args) == 1 {
		dir = ToString(args[0])
	} else {
		throw(argsError)
	}

	cdInner(dir, ec)
}

func cdInner(dir string, ec *evalCtx) {
	err := os.Chdir(dir)
	if err != nil {
		throw(err)
	}
	if ec.store != nil {
		pwd, err := os.Getwd()
		// BUG(xiaq): Possible error of os.Getwd after cd-ing is ignored.
		if err == nil {
			ec.store.AddDir(pwd)
		}
	}
}

func visistedDirs(ec *evalCtx) {
	if ec.store == nil {
		throw(storeNotConnected)
	}
	dirs, err := ec.store.ListDirs()
	if err != nil {
		throw(errors.New("store error: " + err.Error()))
	}
	out := ec.ports[1].ch
	for _, dir := range dirs {
		out <- Map{&map[Value]Value{
			String("path"):  String(dir.Path),
			String("score"): String(fmt.Sprint(dir.Score)),
		}}
	}
}

func jumpDir(ec *evalCtx, arg string) {
	if ec.store == nil {
		throw(storeNotConnected)
	}
	dirs, err := ec.store.FindDirs(arg)
	if err != nil {
		throw(errors.New("store error: " + err.Error()))
	}
	if len(dirs) == 0 {
		throw(noMatchingDir)
	}
	dir := dirs[0].Path
	err = os.Chdir(dir)
	// TODO(xiaq): Remove directories that no longer exist
	if err != nil {
		throw(err)
	}
	ec.store.AddDir(dir)
}

func source(ec *evalCtx, fname string) {
	ec.Source(fname)
}

func toFloat(arg Value) (float64, error) {
	arg, ok := arg.(String)
	if !ok {
		return 0, fmt.Errorf("must be string")
	}
	num, err := strconv.ParseFloat(string(arg.(String)), 64)
	if err != nil {
		return 0, err
	}
	return num, nil
}

func plus(ec *evalCtx, nums ...float64) {
	out := ec.ports[1].ch
	sum := 0.0
	for _, f := range nums {
		sum += f
	}
	out <- String(fmt.Sprintf("%g", sum))
}

func minus(ec *evalCtx, sum float64, nums ...float64) {
	out := ec.ports[1].ch
	for _, f := range nums {
		sum -= f
	}
	out <- String(fmt.Sprintf("%g", sum))
}

func times(ec *evalCtx, nums ...float64) {
	out := ec.ports[1].ch
	prod := 1.0
	for _, f := range nums {
		prod *= f
	}
	out <- String(fmt.Sprintf("%g", prod))
}

func divide(ec *evalCtx, prod float64, nums ...float64) {
	out := ec.ports[1].ch
	for _, f := range nums {
		prod /= f
	}
	out <- String(fmt.Sprintf("%g", prod))
}

func eq(ec *evalCtx, args []Value) {
	out := ec.ports[1].ch
	if len(args) == 0 {
		throw(argsError)
	}
	for i := 0; i+1 < len(args); i++ {
		if args[i] != args[i+1] {
			out <- Bool(false)
			return
		}
	}
	out <- Bool(true)
}

func deepeq(ec *evalCtx, args []Value) {
	out := ec.ports[1].ch
	if len(args) == 0 {
		throw(argsError)
	}
	for i := 0; i+1 < len(args); i++ {
		if !DeepEq(args[i], args[i+1]) {
			out <- Bool(false)
			return
		}
	}
	out <- Bool(true)
}

func bind(ec *evalCtx, key string, function string) {
	if ec.Editor == nil {
		throw(noEditor)
	}
	maybeThrow(ec.Editor.Bind(key, String(function)))
}

func le(ec *evalCtx, name string, args ...Value) {
	if ec.Editor == nil {
		throw(noEditor)
	}
	maybeThrow(ec.Editor.Call(name, args))
}

func _stack(ec *evalCtx) {
	out := ec.ports[1].f

	// XXX dup with main.go
	buf := make([]byte, 1024)
	for runtime.Stack(buf, true) == cap(buf) {
		buf = make([]byte, cap(buf)*2)
	}
	out.Write(buf)
}
<|endoftext|>


<commit_before>/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.

    * Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.

    * Neither the name of "The Computer Language Benchmarks Game" nor the
    name of "The Computer Language Shootout Benchmarks" nor the names of
    its contributors may be used to endorse or promote products derived
    from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/

/* The Computer Language Benchmarks Game
 * http://shootout.alioth.debian.org/
 *
 * contributed by The Go Authors.
 */

package main

import (
	"bufio";
	"os";
)

const lineSize = 60

var complement = [256]uint8{
	'A': 'T', 'a': 'T',
	'C': 'G', 'c': 'G',
	'G': 'C', 'g': 'C',
	'T': 'A', 't': 'A',
	'U': 'A', 'u': 'A',
	'M': 'K', 'm': 'K',
	'R': 'Y', 'r': 'Y',
	'W': 'W', 'w': 'W',
	'S': 'S', 's': 'S',
	'Y': 'R', 'y': 'R',
	'K': 'M', 'k': 'M',
	'V': 'B', 'v': 'B',
	'H': 'D', 'h': 'D',
	'D': 'H', 'd': 'H',
	'B': 'V', 'b': 'V',
	'N': 'N', 'n': 'N',
}

func main() {
	in := bufio.NewReader(os.Stdin);
	buf := make([]byte, 1024*1024);
	line, err := in.ReadSlice('\n');
	for err == nil {
		os.Stdout.Write(line);

		// Accumulate reversed complement in buf[w:]
		nchar := 0;
		w := len(buf);
		for {
			line, err = in.ReadSlice('\n');
			if err != nil || line[0] == '>' {
				break
			}
			line = line[0 : len(line)-1];
			nchar += len(line);
			if len(line)+nchar/60+128 >= w {
				nbuf := make([]byte, len(buf)*5);
				copy(nbuf[len(nbuf)-len(buf):], buf);
				w += len(nbuf) - len(buf);
				buf = nbuf;
			}

			// This loop is the bottleneck.
			for r := 0; r < len(line); r++ {
				w--;
				buf[w] = complement[line[r]];
			}
		}

		// Copy down to beginning of buffer, inserting newlines.
		// The loop left room for the newlines and 128 bytes of padding.
		i := 0;
		for j := w; j < len(buf); j += 60 {
			n := copy(buf[i:i+60], buf[j:]);
			buf[i+n] = '\n';
			i += n + 1;
		}
		os.Stdout.Write(buf[0:i]);
	}
}
<commit_msg>test/bench: use range in reverse-complement<commit_after>/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.

    * Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.

    * Neither the name of "The Computer Language Benchmarks Game" nor the
    name of "The Computer Language Shootout Benchmarks" nor the names of
    its contributors may be used to endorse or promote products derived
    from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/

/* The Computer Language Benchmarks Game
 * http://shootout.alioth.debian.org/
 *
 * contributed by The Go Authors.
 */

package main

import (
	"bufio";
	"os";
)

const lineSize = 60

var complement = [256]uint8{
	'A': 'T', 'a': 'T',
	'C': 'G', 'c': 'G',
	'G': 'C', 'g': 'C',
	'T': 'A', 't': 'A',
	'U': 'A', 'u': 'A',
	'M': 'K', 'm': 'K',
	'R': 'Y', 'r': 'Y',
	'W': 'W', 'w': 'W',
	'S': 'S', 's': 'S',
	'Y': 'R', 'y': 'R',
	'K': 'M', 'k': 'M',
	'V': 'B', 'v': 'B',
	'H': 'D', 'h': 'D',
	'D': 'H', 'd': 'H',
	'B': 'V', 'b': 'V',
	'N': 'N', 'n': 'N',
}

func main() {
	in := bufio.NewReader(os.Stdin);
	buf := make([]byte, 1024*1024);
	line, err := in.ReadSlice('\n');
	for err == nil {
		os.Stdout.Write(line);

		// Accumulate reversed complement in buf[w:]
		nchar := 0;
		w := len(buf);
		for {
			line, err = in.ReadSlice('\n');
			if err != nil || line[0] == '>' {
				break
			}
			line = line[0 : len(line)-1];
			nchar += len(line);
			if len(line)+nchar/60+128 >= w {
				nbuf := make([]byte, len(buf)*5);
				copy(nbuf[len(nbuf)-len(buf):], buf);
				w += len(nbuf) - len(buf);
				buf = nbuf;
			}

			// This loop is the bottleneck.
			for _, c := range line {
				w--;
				buf[w] = complement[c];
			}
		}

		// Copy down to beginning of buffer, inserting newlines.
		// The loop left room for the newlines and 128 bytes of padding.
		i := 0;
		for j := w; j < len(buf); j += 60 {
			n := copy(buf[i:i+60], buf[j:]);
			buf[i+n] = '\n';
			i += n + 1;
		}
		os.Stdout.Write(buf[0:i]);
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes 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 nat

/*
client/server for testing CLOSE_WAIT timeout condition in iptables NAT.

client              server
  |                   |
  |<--tcp handshake-->|
  |<-------fin--------| half-close from server
  |                   | client is in CLOSE_WAIT
*/

import (
	"errors"
	"io"
	"log"
	"net"
	"time"

	"k8s.io/kubernetes/test/images/net/common"
)

// leakedConnection is a
var leakedConnection *net.TCPConn

// Server JSON options.
type CloseWaitServerOptions struct {
	// Address to bind for the test
	LocalAddr string
	// Timeout to wait after sending the FIN.
	PostFinTimeoutSeconds int
}

type closeWaitServer struct {
	options *CloseWaitServerOptions
}

// NewCloseWaitServer returns a new Runner.
func NewCloseWaitServer() common.Runner {
	return &closeWaitServer{}
}

// NewOptions allocates new options structure.
func (server *closeWaitServer) NewOptions() interface{} {
	return &CloseWaitServerOptions{}
}

// Run the server-side of the test.
func (server *closeWaitServer) Run(logger *log.Logger, rawOptions interface{}) error {
	if options, ok := rawOptions.(*CloseWaitServerOptions); ok {
		server.options = options
	} else {
		return errors.New("invalid type")
	}

	logger.Printf("Run %v", server.options)

	addr, err := net.ResolveTCPAddr("tcp", server.options.LocalAddr)
	if err != nil {
		return err
	}

	listener, err := net.ListenTCP("tcp", addr)
	if err != nil {
		return err
	}
	defer listener.Close()

	logger.Printf("Server listening on %v", addr)

	conn, err := listener.AcceptTCP()
	if err != nil {
		return err
	}
	defer conn.Close()

	logger.Printf("Client connected")

	// Send client half-close FIN so client is now in CLOSE_WAIT. We keep
	// the client -> server pipe open to verify whether or not the NAT
	// dropped our connection.
	if err := conn.CloseWrite(); err != nil {
		return err
	}

	logger.Printf("Server sent FIN, waiting %v seconds",
		server.options.PostFinTimeoutSeconds)

	<-time.After(time.Duration(server.options.PostFinTimeoutSeconds) * time.Second)

	logger.Printf("Done")

	return nil
}

// Client JSON options
type CloseWaitClientOptions struct {
	// RemoteAddr of the server to connect to.
	RemoteAddr string
	// TimeoutSeconds on I/O with the server.
	TimeoutSeconds int
	// Half-close timeout (to give the test time to check the status of the
	// conntrack table entry.
	PostFinTimeoutSeconds int
	// Leak connection (assign to global variable so connection persists
	// as long as the process remains.
	LeakConnection bool
}

type closeWaitClient struct {
	options *CloseWaitClientOptions
}

// NewCloseWaitClient creates a new runner
func NewCloseWaitClient() common.Runner {
	return &closeWaitClient{}
}

// NewOptions allocates new options structure.
func (client *closeWaitClient) NewOptions() interface{} {
	return &CloseWaitClientOptions{}
}

// Run the client.m
func (client *closeWaitClient) Run(logger *log.Logger, rawOptions interface{}) error {
	if options, ok := rawOptions.(*CloseWaitClientOptions); ok {
		client.options = options
	} else {
		return errors.New("invalid type")
	}

	logger.Printf("Run %v", client.options)

	addr, err := net.ResolveTCPAddr("tcp", client.options.RemoteAddr)
	if err != nil {
		return err
	}

	conn, err := net.DialTCP("tcp", nil, addr)
	if err != nil {
		return err
	}
	defer conn.Close()

	logger.Printf("Connected to server")

	if client.options.TimeoutSeconds > 0 {
		delay := time.Duration(client.options.TimeoutSeconds) * time.Second
		conn.SetReadDeadline(time.Now().Add(delay))
	}

	buf := make([]byte, 1, 1)
	size, err := conn.Read(buf)

	if err != nil && err != io.EOF {
		return err
	}

	if size != 0 {
		return errors.New("Got data but expected EOF")
	}

	logger.Printf("Server has half-closed the connection, waiting %v seconds",
		client.options.PostFinTimeoutSeconds)

	if client.options.LeakConnection {
		logger.Printf("Leaking client connection (assigning to global variable)")
		leakedConnection = conn
	}

	<-time.After(
		time.Duration(client.options.PostFinTimeoutSeconds) * time.Second)

	logger.Printf("Done")

	return nil
}
<commit_msg>Should not defer conn.Close when leaking the connection<commit_after>/*
Copyright 2016 The Kubernetes 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 nat

/*
client/server for testing CLOSE_WAIT timeout condition in iptables NAT.

client              server
  |                   |
  |<--tcp handshake-->|
  |<-------fin--------| half-close from server
  |                   | client is in CLOSE_WAIT
*/

import (
	"errors"
	"io"
	"log"
	"net"
	"time"

	"k8s.io/kubernetes/test/images/net/common"
)

// leakedConnection is a global variable that should leak the active
// connection assigned here.
var leakedConnection *net.TCPConn

// Server JSON options.
type CloseWaitServerOptions struct {
	// Address to bind for the test
	LocalAddr string
	// Timeout to wait after sending the FIN.
	PostFinTimeoutSeconds int
}

type closeWaitServer struct {
	options *CloseWaitServerOptions
}

// NewCloseWaitServer returns a new Runner.
func NewCloseWaitServer() common.Runner {
	return &closeWaitServer{}
}

// NewOptions allocates new options structure.
func (server *closeWaitServer) NewOptions() interface{} {
	return &CloseWaitServerOptions{}
}

// Run the server-side of the test.
func (server *closeWaitServer) Run(logger *log.Logger, rawOptions interface{}) error {
	if options, ok := rawOptions.(*CloseWaitServerOptions); ok {
		server.options = options
	} else {
		return errors.New("invalid type")
	}

	logger.Printf("Run %v", server.options)

	addr, err := net.ResolveTCPAddr("tcp", server.options.LocalAddr)
	if err != nil {
		return err
	}

	listener, err := net.ListenTCP("tcp", addr)
	if err != nil {
		return err
	}
	defer listener.Close()

	logger.Printf("Server listening on %v", addr)

	conn, err := listener.AcceptTCP()
	if err != nil {
		return err
	}
	defer conn.Close()

	logger.Printf("Client connected")

	// Send client half-close FIN so client is now in CLOSE_WAIT. We keep
	// the client -> server pipe open to verify whether or not the NAT
	// dropped our connection.
	if err := conn.CloseWrite(); err != nil {
		return err
	}

	logger.Printf("Server sent FIN, waiting %v seconds",
		server.options.PostFinTimeoutSeconds)

	<-time.After(time.Duration(server.options.PostFinTimeoutSeconds) * time.Second)

	logger.Printf("Done")

	return nil
}

// Client JSON options
type CloseWaitClientOptions struct {
	// RemoteAddr of the server to connect to.
	RemoteAddr string
	// TimeoutSeconds on I/O with the server.
	TimeoutSeconds int
	// Half-close timeout (to give the test time to check the status of the
	// conntrack table entry.
	PostFinTimeoutSeconds int
	// Leak connection (assign to global variable so connection persists
	// as long as the process remains.
	LeakConnection bool
}

type closeWaitClient struct {
	options *CloseWaitClientOptions
}

// NewCloseWaitClient creates a new runner
func NewCloseWaitClient() common.Runner {
	return &closeWaitClient{}
}

// NewOptions allocates new options structure.
func (client *closeWaitClient) NewOptions() interface{} {
	return &CloseWaitClientOptions{}
}

// Run the client.m
func (client *closeWaitClient) Run(logger *log.Logger, rawOptions interface{}) error {
	if options, ok := rawOptions.(*CloseWaitClientOptions); ok {
		client.options = options
	} else {
		return errors.New("invalid type")
	}

	logger.Printf("Run %v", client.options)

	addr, err := net.ResolveTCPAddr("tcp", client.options.RemoteAddr)
	if err != nil {
		return err
	}

	conn, err := net.DialTCP("tcp", nil, addr)
	if err != nil {
		return err
	}
	if !client.options.LeakConnection {
		defer conn.Close()
	}

	logger.Printf("Connected to server")

	if client.options.TimeoutSeconds > 0 {
		delay := time.Duration(client.options.TimeoutSeconds) * time.Second
		conn.SetReadDeadline(time.Now().Add(delay))
	}

	buf := make([]byte, 1, 1)
	size, err := conn.Read(buf)

	if err != nil && err != io.EOF {
		return err
	}

	if size != 0 {
		return errors.New("Got data but expected EOF")
	}

	logger.Printf("Server has half-closed the connection, waiting %v seconds",
		client.options.PostFinTimeoutSeconds)

	if client.options.LeakConnection {
		logger.Printf("Leaking client connection (assigning to global variable)")
		leakedConnection = conn
	}

	<-time.After(
		time.Duration(client.options.PostFinTimeoutSeconds) * time.Second)

	logger.Printf("Done")

	return nil
}
<|endoftext|>


<commit_before>package engine

import (
	"fmt"
	"protocol"
)

func getJoinYield(table1, table2 string, yield func(*protocol.Series) error) func(*protocol.Series) error {
	var lastPoint1 *protocol.Point
	var lastPoint2 *protocol.Point
	name := table1 + "_join_" + table2

	return mergeYield(table1, table2, func(s *protocol.Series) error {
		if *s.Name == table1 {
			lastPoint1 = s.Points[len(s.Points)-1]
			fmt.Printf("values 1: %#v\n", lastPoint1.Values)
		}

		if *s.Name == table2 {
			lastPoint2 = s.Points[len(s.Points)-1]
			fmt.Printf("values 2: %#v\n", lastPoint2.Values)
		}

		if lastPoint1 == nil || lastPoint2 == nil {
			return nil
		}

		// join the last two points and yield them
		newValues := []*protocol.FieldValue{}
		for idx, value := range lastPoint1.Values {
			if value == nil {
				value = lastPoint2.Values[idx]
			}
			newValues = append(newValues, value)
		}

		newSeries := &protocol.Series{
			Name:   &name,
			Fields: s.Fields,
			Points: []*protocol.Point{
				&protocol.Point{
					Values:         newValues,
					Timestamp:      lastPoint2.Timestamp,
					SequenceNumber: lastPoint2.SequenceNumber,
				},
			},
		}

		lastPoint1 = nil
		lastPoint2 = nil

		return yield(newSeries)
	})
}

func getMergeYield(table1, table2 string, yield func(*protocol.Series) error) func(*protocol.Series) error {
	name := table1 + "_merge_" + table2

	return mergeYield(table1, table2, func(s *protocol.Series) error {
		s.Name = &name
		return yield(s)
	})
}

type mergeState struct {
	name       string
	series     []*protocol.Series
	fields     []string
	nullValues []*protocol.FieldValue
	isLeft     bool
	done       bool
}

func (self *mergeState) hasPoints() bool {
	return len(self.series) > 0 && len(self.series[0].Points) > 0
}

func (self *mergeState) isEarlier(other *mergeState) bool {
	return *self.series[0].Points[0].Timestamp < *other.series[0].Points[0].Timestamp
}

// set the fields of the other time series to null making sure that
// the order of the null values match the order of the field
// definitions, i.e. left timeseries first followed by values from the
// right timeseries
func (self *mergeState) mergeValues(other *mergeState, p *protocol.Point) {
	if self.isLeft {
		p.Values = append(p.Values, other.nullValues...)
		return
	}
	p.Values = append(other.nullValues, p.Values...)
}

// if `other` state is done (i.e. we'll receive no more points for its
// timeseries) then we know that we won't get any points that are
// older than what's in `self` so we can safely flush all `self`
// points.
func (self *mergeState) flushIfOtherStateIsEmpty(other *mergeState, fields []string, yield func(*protocol.Series) error) error {
	if other.done && len(other.series) == 0 {
		for _, s := range self.series {
			for _, p := range s.Points {
				self.mergeValues(other, p)
			}

			err := yield(&protocol.Series{
				Name:   s.Name,
				Fields: fields,
				Points: s.Points,
			})
			if err != nil {
				return err
			}
		}
		self.series = nil
	}
	return nil
}

// update the state, the points belong to this mergeState (i.e. the name of the timeseries matches)
func (self *mergeState) updateState(p *protocol.Series) {
	if *p.Name != self.name {
		return
	}

	// setup the fields
	if self.fields == nil {
		for _, f := range p.Fields {
			self.fields = append(self.fields, self.name+"."+f)
		}
		for i := 0; i < len(p.Fields); i++ {
			self.nullValues = append(self.nullValues, nil)
		}
	}

	// data for current table is exhausted
	if len(p.Points) == 0 {
		self.done = true
	} else {
		self.series = append(self.series, p)
	}
}

func (self *mergeState) removeAndGetFirstPoint() *protocol.Point {
	point := self.series[0].Points[0]
	// get rid of that point, or get rid of the entire series
	// if this is the last point
	if len(self.series[0].Points) == 1 {
		self.series = self.series[1:]
	} else {
		self.series[0].Points = self.series[0].Points[1:]
	}
	return point
}

// returns a yield function that will sort points from table1 and
// table2 no matter what the order in which they are received.
//
// FIXME: This won't work for queries that are executed in descending order.
func mergeYield(table1, table2 string, yield func(*protocol.Series) error) func(*protocol.Series) error {
	state1 := &mergeState{
		name:   table1,
		isLeft: true,
	}
	state2 := &mergeState{
		name:   table2,
		isLeft: false,
	}

	return func(p *protocol.Series) error {
		state1.updateState(p)
		state2.updateState(p)

		mergedFields := append(state1.fields, state2.fields...)

		if err := state1.flushIfOtherStateIsEmpty(state2, mergedFields, yield); err != nil {
			return err
		}
		if err := state2.flushIfOtherStateIsEmpty(state1, mergedFields, yield); err != nil {
			return err
		}

		// see which point should be returned next and remove it from the
		// series
		for state1.hasPoints() && state2.hasPoints() {
			// state is the state of the series from which the next point
			// will be fetched
			state := state1
			otherState := state2
			if state2.isEarlier(state1) {
				state = state2
				otherState = state1
			}

			p := state.removeAndGetFirstPoint()
			state.mergeValues(otherState, p)

			err := yield(&protocol.Series{
				Name:   &state.name,
				Fields: mergedFields,
				Points: []*protocol.Point{p},
			})
			if err != nil {
				return err
			}
		}
		return nil
	}
}
<commit_msg>remove some printfs.<commit_after>package engine

import (
	"protocol"
)

func getJoinYield(table1, table2 string, yield func(*protocol.Series) error) func(*protocol.Series) error {
	var lastPoint1 *protocol.Point
	var lastPoint2 *protocol.Point
	name := table1 + "_join_" + table2

	return mergeYield(table1, table2, func(s *protocol.Series) error {
		if *s.Name == table1 {
			lastPoint1 = s.Points[len(s.Points)-1]
		}

		if *s.Name == table2 {
			lastPoint2 = s.Points[len(s.Points)-1]
		}

		if lastPoint1 == nil || lastPoint2 == nil {
			return nil
		}

		// join the last two points and yield them
		newValues := []*protocol.FieldValue{}
		for idx, value := range lastPoint1.Values {
			if value == nil {
				value = lastPoint2.Values[idx]
			}
			newValues = append(newValues, value)
		}

		newSeries := &protocol.Series{
			Name:   &name,
			Fields: s.Fields,
			Points: []*protocol.Point{
				&protocol.Point{
					Values:         newValues,
					Timestamp:      lastPoint2.Timestamp,
					SequenceNumber: lastPoint2.SequenceNumber,
				},
			},
		}

		lastPoint1 = nil
		lastPoint2 = nil

		return yield(newSeries)
	})
}

func getMergeYield(table1, table2 string, yield func(*protocol.Series) error) func(*protocol.Series) error {
	name := table1 + "_merge_" + table2

	return mergeYield(table1, table2, func(s *protocol.Series) error {
		s.Name = &name
		return yield(s)
	})
}

type mergeState struct {
	name       string
	series     []*protocol.Series
	fields     []string
	nullValues []*protocol.FieldValue
	isLeft     bool
	done       bool
}

func (self *mergeState) hasPoints() bool {
	return len(self.series) > 0 && len(self.series[0].Points) > 0
}

func (self *mergeState) isEarlier(other *mergeState) bool {
	return *self.series[0].Points[0].Timestamp < *other.series[0].Points[0].Timestamp
}

// set the fields of the other time series to null making sure that
// the order of the null values match the order of the field
// definitions, i.e. left timeseries first followed by values from the
// right timeseries
func (self *mergeState) mergeValues(other *mergeState, p *protocol.Point) {
	if self.isLeft {
		p.Values = append(p.Values, other.nullValues...)
		return
	}
	p.Values = append(other.nullValues, p.Values...)
}

// if `other` state is done (i.e. we'll receive no more points for its
// timeseries) then we know that we won't get any points that are
// older than what's in `self` so we can safely flush all `self`
// points.
func (self *mergeState) flushIfOtherStateIsEmpty(other *mergeState, fields []string, yield func(*protocol.Series) error) error {
	if other.done && len(other.series) == 0 {
		for _, s := range self.series {
			for _, p := range s.Points {
				self.mergeValues(other, p)
			}

			err := yield(&protocol.Series{
				Name:   s.Name,
				Fields: fields,
				Points: s.Points,
			})
			if err != nil {
				return err
			}
		}
		self.series = nil
	}
	return nil
}

// update the state, the points belong to this mergeState (i.e. the name of the timeseries matches)
func (self *mergeState) updateState(p *protocol.Series) {
	if *p.Name != self.name {
		return
	}

	// setup the fields
	if self.fields == nil {
		for _, f := range p.Fields {
			self.fields = append(self.fields, self.name+"."+f)
		}
		for i := 0; i < len(p.Fields); i++ {
			self.nullValues = append(self.nullValues, nil)
		}
	}

	// data for current table is exhausted
	if len(p.Points) == 0 {
		self.done = true
	} else {
		self.series = append(self.series, p)
	}
}

func (self *mergeState) removeAndGetFirstPoint() *protocol.Point {
	point := self.series[0].Points[0]
	// get rid of that point, or get rid of the entire series
	// if this is the last point
	if len(self.series[0].Points) == 1 {
		self.series = self.series[1:]
	} else {
		self.series[0].Points = self.series[0].Points[1:]
	}
	return point
}

// returns a yield function that will sort points from table1 and
// table2 no matter what the order in which they are received.
//
// FIXME: This won't work for queries that are executed in descending order.
func mergeYield(table1, table2 string, yield func(*protocol.Series) error) func(*protocol.Series) error {
	state1 := &mergeState{
		name:   table1,
		isLeft: true,
	}
	state2 := &mergeState{
		name:   table2,
		isLeft: false,
	}

	return func(p *protocol.Series) error {
		state1.updateState(p)
		state2.updateState(p)

		mergedFields := append(state1.fields, state2.fields...)

		if err := state1.flushIfOtherStateIsEmpty(state2, mergedFields, yield); err != nil {
			return err
		}
		if err := state2.flushIfOtherStateIsEmpty(state1, mergedFields, yield); err != nil {
			return err
		}

		// see which point should be returned next and remove it from the
		// series
		for state1.hasPoints() && state2.hasPoints() {
			// state is the state of the series from which the next point
			// will be fetched
			state := state1
			otherState := state2
			if state2.isEarlier(state1) {
				state = state2
				otherState = state1
			}

			p := state.removeAndGetFirstPoint()
			state.mergeValues(otherState, p)

			err := yield(&protocol.Series{
				Name:   &state.name,
				Fields: mergedFields,
				Points: []*protocol.Point{p},
			})
			if err != nil {
				return err
			}
		}
		return nil
	}
}
<|endoftext|>


<commit_before>package display

import (
	"fmt"
	"io"
	"os"
	"reflect"
	"sort"
	"strings"
	"time"
	"unicode/utf8"

	"github.com/fatih/color"
	"github.com/olekukonko/tablewriter"
	"github.com/wallix/awless/cloud/aws"
	"github.com/wallix/awless/rdf"
)

type GraphDisplayer interface {
	sorter
	Print(io.Writer) error
	SetGraph(*rdf.Graph)
}

type sorter interface {
	sort(table)
	columns() []int
}

type builder struct {
	headers  []ColumnDefinition
	format   string
	rdfType  rdf.ResourceType
	sort     []int
	maxWidth int
}

type optsFn func(b *builder) *builder

func BuildDisplayer(opts ...optsFn) GraphDisplayer {
	b := &builder{}

	b.sort = []int{0}
	b.format = "table"

	for _, fn := range opts {
		fn(b)
	}

	if len(b.headers) == 0 {
		b.headers = DefaultsColumnDefinitions[b.rdfType]
	}

	switch b.format {
	case "csv":
		return &csvGraphDisplayer{sorter: &defaultSorter{sortBy: b.sort}, rdfType: b.rdfType, headers: b.headers}
	case "table":
		return &tableGraphDisplayer{sorter: &defaultSorter{sortBy: b.sort}, rdfType: b.rdfType, headers: b.headers, maxwidth: b.maxWidth}
	case "porcelain":
		return &porcelainGraphDisplayer{sorter: &defaultSorter{sortBy: b.sort}, rdfType: b.rdfType, headers: b.headers}
	default:
		fmt.Fprintf(os.Stderr, "unknown format '%s', display as 'table'\n", b.format)
		return &tableGraphDisplayer{sorter: &defaultSorter{sortBy: b.sort}, rdfType: b.rdfType, headers: b.headers, maxwidth: b.maxWidth}
	}
}

func WithFormat(format string) optsFn {
	return func(b *builder) *builder {
		b.format = format
		return b
	}
}

func WithHeaders(h []ColumnDefinition) optsFn {
	return func(b *builder) *builder {
		b.headers = h
		return b
	}
}

func WithIDsOnly(only bool) optsFn {
	return func(b *builder) *builder {
		if only {
			b.headers = []ColumnDefinition{
				StringColumnDefinition{Prop: "Id"},
				StringColumnDefinition{Prop: "Name"},
			}
			b.format = "porcelain"
		}

		return b
	}
}

func WithSortBy(sortingBy ...string) optsFn {
	return func(b *builder) *builder {
		indexes, err := resolveSortIndexes(b.headers, sortingBy...)
		if err != nil {
			fmt.Fprint(os.Stderr, err, "\n")
		}

		b.sort = indexes

		return b
	}
}

func WithMaxWidth(maxwidth int) optsFn {
	return func(b *builder) *builder {
		b.maxWidth = maxwidth
		return b
	}
}

func WithRdfType(rdfType rdf.ResourceType) optsFn {
	return func(b *builder) *builder {
		b.rdfType = rdfType
		return b
	}
}

type table [][]interface{}

type csvGraphDisplayer struct {
	sorter
	g       *rdf.Graph
	rdfType rdf.ResourceType
	headers []ColumnDefinition
}

func (d *csvGraphDisplayer) Print(w io.Writer) error {
	resources, err := aws.LoadResourcesFromGraph(d.g, d.rdfType)
	if err != nil {
		return err
	}

	values := make(table, len(resources))
	for i, res := range resources {
		if v := values[i]; v == nil {
			values[i] = make([]interface{}, len(d.headers))
		}
		for j, h := range d.headers {
			values[i][j] = res.Properties()[h.propKey()]
		}
	}

	d.sorter.sort(values)

	var lines []string

	var head []string
	for _, h := range d.headers {
		head = append(head, h.title(false))
	}

	lines = append(lines, strings.Join(head, ", "))

	for i := range values {
		var props []string
		for j, h := range d.headers {
			props = append(props, h.format(values[i][j]))
		}
		lines = append(lines, strings.Join(props, ", "))
	}

	_, err = w.Write([]byte(strings.Join(lines, "\n")))
	return err
}

func (d *csvGraphDisplayer) SetGraph(g *rdf.Graph) {
	d.g = g
}

type tableGraphDisplayer struct {
	sorter
	g        *rdf.Graph
	rdfType  rdf.ResourceType
	headers  []ColumnDefinition
	maxwidth int
}

func (d *tableGraphDisplayer) Print(w io.Writer) error {
	resources, err := aws.LoadResourcesFromGraph(d.g, d.rdfType)
	if err != nil {
		return err
	}

	values := make(table, len(resources))
	for i, res := range resources {
		if v := values[i]; v == nil {
			values[i] = make([]interface{}, len(d.headers))
		}
		for j, h := range d.headers {
			values[i][j] = res.Properties()[h.propKey()]
		}
	}

	d.sorter.sort(values)

	columnsToDisplay := d.headers
	if d.maxwidth != 0 {
		columnsToDisplay = []ColumnDefinition{}
		currentWidth := 0
		for j, h := range d.headers {
			colW := columnWidth(j, values, h) + 2 // +2 (tables margin)
			if currentWidth+colW > d.maxwidth {
				break
			}
			currentWidth += colW
			columnsToDisplay = append(columnsToDisplay, h)
		}
	}

	markColumnAsc := -1
	if len(d.sorter.columns()) > 0 {
		markColumnAsc = d.sorter.columns()[0]
	}

	table := tablewriter.NewWriter(w)
	var displayHeaders []string
	for i, h := range columnsToDisplay {
		displayHeaders = append(displayHeaders, h.title(i == markColumnAsc))
	}
	table.SetHeader(displayHeaders)

	for i := range values {
		var props []string
		for j, h := range columnsToDisplay {
			props = append(props, h.format(values[i][j]))
		}
		table.Append(props)
	}

	table.Render()
	if len(columnsToDisplay) < len(d.headers) {
		var hiddenColumns []string
		for i := len(columnsToDisplay); i < len(d.headers); i++ {
			hiddenColumns = append(hiddenColumns, "'"+d.headers[i].title(false)+"'")
		}
		if len(hiddenColumns) == 1 {
			fmt.Fprint(w, color.New(color.FgRed).SprintfFunc()("Column truncated to fit terminal: %s\n", hiddenColumns[0]))
		} else {
			fmt.Fprint(w, color.New(color.FgRed).SprintfFunc()("Columns truncated to fit terminal: %s\n", strings.Join(hiddenColumns, ", ")))
		}
	}
	return nil
}

func (d *tableGraphDisplayer) SetGraph(g *rdf.Graph) {
	d.g = g
}

type porcelainGraphDisplayer struct {
	sorter
	g       *rdf.Graph
	rdfType rdf.ResourceType
	headers []ColumnDefinition
}

func (d *porcelainGraphDisplayer) Print(w io.Writer) error {
	resources, err := aws.LoadResourcesFromGraph(d.g, d.rdfType)
	if err != nil {
		return err
	}

	values := make(table, len(resources))
	for i, res := range resources {
		if v := values[i]; v == nil {
			values[i] = make([]interface{}, len(d.headers))
		}
		for j, h := range d.headers {
			values[i][j] = res.Properties()[h.propKey()]
		}
	}

	d.sorter.sort(values)

	var lines []string

	for i := range values {
		for j, h := range d.headers {
			val := h.format(values[i][j])
			if val != "" {
				lines = append(lines, val)
			}
		}
	}

	_, err = w.Write([]byte(strings.Join(lines, "\n")))
	return err
}

func (d *porcelainGraphDisplayer) SetGraph(g *rdf.Graph) {
	d.g = g
}

type defaultSorter struct {
	sortBy []int
}

func (d *defaultSorter) sort(lines table) {
	sort.Sort(byCols{table: lines, sortBy: d.sortBy})
}

func (d *defaultSorter) columns() []int {
	return d.sortBy
}

type byCols struct {
	table  table
	sortBy []int
}

func (b byCols) Len() int { return len(b.table) }
func (b byCols) Swap(i, j int) {
	b.table[i], b.table[j] = b.table[j], b.table[i]
}
func (b byCols) Less(i, j int) bool {
	for _, col := range b.sortBy {
		if reflect.DeepEqual(b.table[i][col], b.table[j][col]) {
			continue
		}
		return valueLowerOrEqual(b.table[i][col], b.table[j][col])
	}
	return false
}

func valueLowerOrEqual(a, b interface{}) bool {
	if a == b {
		return true
	}
	if a == nil {
		return true
	}
	if b == nil {
		return false
	}
	if reflect.TypeOf(a) != reflect.TypeOf(b) {
		panic(fmt.Sprintf("can not compare values of type %T and %T", a, b))
	}
	switch a.(type) {
	case int:
		aa := a.(int)
		bb := b.(int)
		return aa <= bb
	case float64:
		aa := a.(float64)
		bb := b.(float64)
		return aa <= bb
	case string:
		aa := a.(string)
		bb := b.(string)
		return aa <= bb
	case time.Time:
		aa := a.(time.Time)
		bb := b.(time.Time)
		return aa.Before(bb)
	default:
		panic(fmt.Sprintf("can not compare values of type %T", a))
	}
}

func resolveSortIndexes(headers []ColumnDefinition, sortingBy ...string) ([]int, error) {
	sortBy := []string{"id"}
	if len(sortingBy) > 0 {
		sortBy = sortingBy
	}

	normalized := make(map[string]int)
	for i, h := range headers {
		normalized[strings.ToLower(h.title(false))] = i
	}

	var ids []int
	for _, t := range sortBy {
		id, ok := normalized[strings.ToLower(t)]
		if !ok {
			return ids, fmt.Errorf("Invalid column name '%s'", t)
		}
		ids = append(ids, id)
	}

	return ids, nil
}

func columnWidth(j int, t table, h ColumnDefinition) int {
	w := 0
	for i := range t {
		c := utf8.RuneCountInString(h.format(t[i][j]))
		if c > w {
			w = c
		}
	}
	return w
}
<commit_msg>Display: removing duplicated fields<commit_after>package display

import (
	"fmt"
	"io"
	"os"
	"reflect"
	"sort"
	"strings"
	"time"
	"unicode/utf8"

	"github.com/fatih/color"
	"github.com/olekukonko/tablewriter"
	"github.com/wallix/awless/cloud/aws"
	"github.com/wallix/awless/rdf"
)

type GraphDisplayer interface {
	sorter
	Print(io.Writer) error
	SetGraph(*rdf.Graph)
}

type sorter interface {
	sort(table)
	columns() []int
}

type builder struct {
	headers  []ColumnDefinition
	format   string
	rdfType  rdf.ResourceType
	sort     []int
	maxwidth int
}

type optsFn func(b *builder) *builder

func BuildDisplayer(opts ...optsFn) GraphDisplayer {
	b := &builder{}

	b.sort = []int{0}
	b.format = "table"

	for _, fn := range opts {
		fn(b)
	}

	if len(b.headers) == 0 {
		b.headers = DefaultsColumnDefinitions[b.rdfType]
	}

	base := fromGraphDisplayer{sorter: &defaultSorter{sortBy: b.sort}, rdfType: b.rdfType, headers: b.headers, maxwidth: b.maxwidth}

	switch b.format {
	case "csv":
		return &csvDisplayer{base}
	case "porcelain":
		return &porcelainDisplayer{base}
	case "table":
		return &tableDisplayer{base}
	default:
		fmt.Fprintf(os.Stderr, "unknown format '%s', display as 'table'\n", b.format)
		return &tableDisplayer{base}
	}
}

func WithFormat(format string) optsFn {
	return func(b *builder) *builder {
		b.format = format
		return b
	}
}

func WithHeaders(h []ColumnDefinition) optsFn {
	return func(b *builder) *builder {
		b.headers = h
		return b
	}
}

func WithIDsOnly(only bool) optsFn {
	return func(b *builder) *builder {
		if only {
			b.headers = []ColumnDefinition{
				StringColumnDefinition{Prop: "Id"},
				StringColumnDefinition{Prop: "Name"},
			}
			b.format = "porcelain"
		}

		return b
	}
}

func WithSortBy(sortingBy ...string) optsFn {
	return func(b *builder) *builder {
		indexes, err := resolveSortIndexes(b.headers, sortingBy...)
		if err != nil {
			fmt.Fprint(os.Stderr, err, "\n")
		}

		b.sort = indexes

		return b
	}
}

func WithMaxWidth(maxwidth int) optsFn {
	return func(b *builder) *builder {
		b.maxwidth = maxwidth
		return b
	}
}

func WithRdfType(rdfType rdf.ResourceType) optsFn {
	return func(b *builder) *builder {
		b.rdfType = rdfType
		return b
	}
}

type table [][]interface{}

type fromGraphDisplayer struct {
	sorter
	g       *rdf.Graph
	rdfType rdf.ResourceType
	headers []ColumnDefinition
	maxwidth int
}

type csvDisplayer struct {
	fromGraphDisplayer
}

func (d *csvDisplayer) Print(w io.Writer) error {
	resources, err := aws.LoadResourcesFromGraph(d.g, d.rdfType)
	if err != nil {
		return err
	}

	values := make(table, len(resources))
	for i, res := range resources {
		if v := values[i]; v == nil {
			values[i] = make([]interface{}, len(d.headers))
		}
		for j, h := range d.headers {
			values[i][j] = res.Properties()[h.propKey()]
		}
	}

	d.sorter.sort(values)

	var lines []string

	var head []string
	for _, h := range d.headers {
		head = append(head, h.title(false))
	}

	lines = append(lines, strings.Join(head, ", "))

	for i := range values {
		var props []string
		for j, h := range d.headers {
			props = append(props, h.format(values[i][j]))
		}
		lines = append(lines, strings.Join(props, ", "))
	}

	_, err = w.Write([]byte(strings.Join(lines, "\n")))
	return err
}

func (d *csvDisplayer) SetGraph(g *rdf.Graph) {
	d.g = g
}

type tableDisplayer struct {
	fromGraphDisplayer
}

func (d *tableDisplayer) Print(w io.Writer) error {
	resources, err := aws.LoadResourcesFromGraph(d.g, d.rdfType)
	if err != nil {
		return err
	}

	values := make(table, len(resources))
	for i, res := range resources {
		if v := values[i]; v == nil {
			values[i] = make([]interface{}, len(d.headers))
		}
		for j, h := range d.headers {
			values[i][j] = res.Properties()[h.propKey()]
		}
	}

	d.sorter.sort(values)

	columnsToDisplay := d.headers
	if d.maxwidth != 0 {
		columnsToDisplay = []ColumnDefinition{}
		currentWidth := 0
		for j, h := range d.headers {
			colW := t (j, values, h) + 2 // +2 (tables margin)
			if currentWidth+colW > d.maxwidth {
				break
			}
			currentWidth += colW
			columnsToDisplay = append(columnsToDisplay, h)
		}
	}

	markColumnAsc := -1
	if len(d.sorter.columns()) > 0 {
		markColumnAsc = d.sorter.columns()[0]
	}

	table := tablewriter.NewWriter(w)
	var displayHeaders []string
	for i, h := range columnsToDisplay {
		displayHeaders = append(displayHeaders, h.title(i == markColumnAsc))
	}
	table.SetHeader(displayHeaders)

	for i := range values {
		var props []string
		for j, h := range columnsToDisplay {
			props = append(props, h.format(values[i][j]))
		}
		table.Append(props)
	}

	table.Render()
	if len(columnsToDisplay) < len(d.headers) {
		var hiddenColumns []string
		for i := len(columnsToDisplay); i < len(d.headers); i++ {
			hiddenColumns = append(hiddenColumns, "'"+d.headers[i].title(false)+"'")
		}
		if len(hiddenColumns) == 1 {
			fmt.Fprint(w, color.New(color.FgRed).SprintfFunc()("Column truncated to fit terminal: %s\n", hiddenColumns[0]))
		} else {
			fmt.Fprint(w, color.New(color.FgRed).SprintfFunc()("Columns truncated to fit terminal: %s\n", strings.Join(hiddenColumns, ", ")))
		}
	}
	return nil
}

func (d *tableDisplayer) SetGraph(g *rdf.Graph) {
	d.g = g
}

type porcelainDisplayer struct {
	fromGraphDisplayer
}

func (d *porcelainDisplayer) Print(w io.Writer) error {
	resources, err := aws.LoadResourcesFromGraph(d.g, d.rdfType)
	if err != nil {
		return err
	}

	values := make(table, len(resources))
	for i, res := range resources {
		if v := values[i]; v == nil {
			values[i] = make([]interface{}, len(d.headers))
		}
		for j, h := range d.headers {
			values[i][j] = res.Properties()[h.propKey()]
		}
	}

	d.sorter.sort(values)

	var lines []string

	for i := range values {
		for j, h := range d.headers {
			val := h.format(values[i][j])
			if val != "" {
				lines = append(lines, val)
			}
		}
	}

	_, err = w.Write([]byte(strings.Join(lines, "\n")))
	return err
}

func (d *porcelainDisplayer) SetGraph(g *rdf.Graph) {
	d.g = g
}

type defaultSorter struct {
	sortBy []int
}

func (d *defaultSorter) sort(lines table) {
	sort.Sort(byCols{table: lines, sortBy: d.sortBy})
}

func (d *defaultSorter) columns() []int {
	return d.sortBy
}

type byCols struct {
	table  table
	sortBy []int
}

func (b byCols) Len() int { return len(b.table) }
func (b byCols) Swap(i, j int) {
	b.table[i], b.table[j] = b.table[j], b.table[i]
}
func (b byCols) Less(i, j int) bool {
	for _, col := range b.sortBy {
		if reflect.DeepEqual(b.table[i][col], b.table[j][col]) {
			continue
		}
		return valueLowerOrEqual(b.table[i][col], b.table[j][col])
	}
	return false
}

func valueLowerOrEqual(a, b interface{}) bool {
	if a == b {
		return true
	}
	if a == nil {
		return true
	}
	if b == nil {
		return false
	}
	if reflect.TypeOf(a) != reflect.TypeOf(b) {
		panic(fmt.Sprintf("can not compare values of type %T and %T", a, b))
	}
	switch a.(type) {
	case int:
		aa := a.(int)
		bb := b.(int)
		return aa <= bb
	case float64:
		aa := a.(float64)
		bb := b.(float64)
		return aa <= bb
	case string:
		aa := a.(string)
		bb := b.(string)
		return aa <= bb
	case time.Time:
		aa := a.(time.Time)
		bb := b.(time.Time)
		return aa.Before(bb)
	default:
		panic(fmt.Sprintf("can not compare values of type %T", a))
	}
}

func resolveSortIndexes(headers []ColumnDefinition, sortingBy ...string) ([]int, error) {
	sortBy := []string{"id"}
	if len(sortingBy) > 0 {
		sortBy = sortingBy
	}

	normalized := make(map[string]int)
	for i, h := range headers {
		normalized[strings.ToLower(h.title(false))] = i
	}

	var ids []int
	for _, t := range sortBy {
		id, ok := normalized[strings.ToLower(t)]
		if !ok {
			return ids, fmt.Errorf("Invalid column name '%s'", t)
		}
		ids = append(ids, id)
	}

	return ids, nil
}

func t (j int, t table, h ColumnDefinition) int {
	w := 0
	for i := range t {
		c := utf8.RuneCountInString(h.format(t[i][j]))
		if c > w {
			w = c
		}
	}
	return w
}
<|endoftext|>


<commit_before>// Copyright 2016 The etcd 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 cache

import (
	"errors"
	"sync"

	"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
	"github.com/coreos/etcd/pkg/adt"
	"github.com/golang/groupcache/lru"
)

var (
	DefaultMaxEntries = 2048
	ErrCompacted      = rpctypes.ErrGRPCCompacted
)

type Cache interface {
	Add(req *pb.RangeRequest, resp *pb.RangeResponse)
	Get(req *pb.RangeRequest) (*pb.RangeResponse, error)
	Compact(revision int64)
	Invalidate(key []byte, endkey []byte)
	Size() int
	Close()
}

// keyFunc returns the key of an request, which is used to look up in the cache for it's caching response.
func keyFunc(req *pb.RangeRequest) string {
	// TODO: use marshalTo to reduce allocation
	b, err := req.Marshal()
	if err != nil {
		panic(err)
	}
	return string(b)
}

func NewCache(maxCacheEntries int) Cache {
	return &cache{
		lru:          lru.New(maxCacheEntries),
		compactedRev: -1,
	}
}

func (c *cache) Close() {}

// cache implements Cache
type cache struct {
	mu  sync.RWMutex
	lru *lru.Cache

	// a reverse index for cache invalidation
	cachedRanges adt.IntervalTree

	compactedRev int64
}

// Add adds the response of a request to the cache if its revision is larger than the compacted revision of the cache.
func (c *cache) Add(req *pb.RangeRequest, resp *pb.RangeResponse) {
	key := keyFunc(req)

	c.mu.Lock()
	defer c.mu.Unlock()

	if req.Revision > c.compactedRev {
		c.lru.Add(key, resp)
	}
	// we do not need to invalidate a request with a revision specified.
	// so we do not need to add it into the reverse index.
	if req.Revision != 0 {
		return
	}

	var (
		iv  *adt.IntervalValue
		ivl adt.Interval
	)
	if len(req.RangeEnd) != 0 {
		ivl = adt.NewStringAffineInterval(string(req.Key), string(req.RangeEnd))
	} else {
		ivl = adt.NewStringAffinePoint(string(req.Key))
	}

	iv = c.cachedRanges.Find(ivl)

	if iv == nil {
		c.cachedRanges.Insert(ivl, []string{key})
	} else {
		iv.Val = append(iv.Val.([]string), key)
	}
}

// Get looks up the caching response for a given request.
// Get is also responsible for lazy eviction when accessing compacted entries.
func (c *cache) Get(req *pb.RangeRequest) (*pb.RangeResponse, error) {
	key := keyFunc(req)

	c.mu.Lock()
	defer c.mu.Unlock()

	if req.Revision < c.compactedRev {
		c.lru.Remove(key)
		return nil, ErrCompacted
	}

	if resp, ok := c.lru.Get(key); ok {
		return resp.(*pb.RangeResponse), nil
	}
	return nil, errors.New("not exist")
}

// Invalidate invalidates the cache entries that intersecting with the given range from key to endkey.
func (c *cache) Invalidate(key, endkey []byte) {
	c.mu.Lock()
	defer c.mu.Unlock()

	var (
		ivs []*adt.IntervalValue
		ivl adt.Interval
	)
	if len(endkey) == 0 {
		ivl = adt.NewStringAffinePoint(string(key))
	} else {
		ivl = adt.NewStringAffineInterval(string(key), string(endkey))
	}

	ivs = c.cachedRanges.Stab(ivl)
	for _, iv := range ivs {
		keys := iv.Val.([]string)
		for _, key := range keys {
			c.lru.Remove(key)
		}
	}
	// delete after removing all keys since it is destructive to 'ivs'
	c.cachedRanges.Delete(ivl)
}

// Compact invalidate all caching response before the given rev.
// Replace with the invalidation is lazy. The actual removal happens when the entries is accessed.
func (c *cache) Compact(revision int64) {
	c.mu.Lock()
	defer c.mu.Unlock()

	if revision > c.compactedRev {
		c.compactedRev = revision
	}
}

func (c *cache) Size() int {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.lru.Len()
}
<commit_msg>grpcproxy/cache: only check compaction revision for historical revisions<commit_after>// Copyright 2016 The etcd 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 cache

import (
	"errors"
	"sync"

	"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
	"github.com/coreos/etcd/pkg/adt"
	"github.com/golang/groupcache/lru"
)

var (
	DefaultMaxEntries = 2048
	ErrCompacted      = rpctypes.ErrGRPCCompacted
)

type Cache interface {
	Add(req *pb.RangeRequest, resp *pb.RangeResponse)
	Get(req *pb.RangeRequest) (*pb.RangeResponse, error)
	Compact(revision int64)
	Invalidate(key []byte, endkey []byte)
	Size() int
	Close()
}

// keyFunc returns the key of an request, which is used to look up in the cache for it's caching response.
func keyFunc(req *pb.RangeRequest) string {
	// TODO: use marshalTo to reduce allocation
	b, err := req.Marshal()
	if err != nil {
		panic(err)
	}
	return string(b)
}

func NewCache(maxCacheEntries int) Cache {
	return &cache{
		lru:          lru.New(maxCacheEntries),
		compactedRev: -1,
	}
}

func (c *cache) Close() {}

// cache implements Cache
type cache struct {
	mu  sync.RWMutex
	lru *lru.Cache

	// a reverse index for cache invalidation
	cachedRanges adt.IntervalTree

	compactedRev int64
}

// Add adds the response of a request to the cache if its revision is larger than the compacted revision of the cache.
func (c *cache) Add(req *pb.RangeRequest, resp *pb.RangeResponse) {
	key := keyFunc(req)

	c.mu.Lock()
	defer c.mu.Unlock()

	if req.Revision > c.compactedRev {
		c.lru.Add(key, resp)
	}
	// we do not need to invalidate a request with a revision specified.
	// so we do not need to add it into the reverse index.
	if req.Revision != 0 {
		return
	}

	var (
		iv  *adt.IntervalValue
		ivl adt.Interval
	)
	if len(req.RangeEnd) != 0 {
		ivl = adt.NewStringAffineInterval(string(req.Key), string(req.RangeEnd))
	} else {
		ivl = adt.NewStringAffinePoint(string(req.Key))
	}

	iv = c.cachedRanges.Find(ivl)

	if iv == nil {
		c.cachedRanges.Insert(ivl, []string{key})
	} else {
		iv.Val = append(iv.Val.([]string), key)
	}
}

// Get looks up the caching response for a given request.
// Get is also responsible for lazy eviction when accessing compacted entries.
func (c *cache) Get(req *pb.RangeRequest) (*pb.RangeResponse, error) {
	key := keyFunc(req)

	c.mu.Lock()
	defer c.mu.Unlock()

	if req.Revision > 0 && req.Revision < c.compactedRev {
		c.lru.Remove(key)
		return nil, ErrCompacted
	}

	if resp, ok := c.lru.Get(key); ok {
		return resp.(*pb.RangeResponse), nil
	}
	return nil, errors.New("not exist")
}

// Invalidate invalidates the cache entries that intersecting with the given range from key to endkey.
func (c *cache) Invalidate(key, endkey []byte) {
	c.mu.Lock()
	defer c.mu.Unlock()

	var (
		ivs []*adt.IntervalValue
		ivl adt.Interval
	)
	if len(endkey) == 0 {
		ivl = adt.NewStringAffinePoint(string(key))
	} else {
		ivl = adt.NewStringAffineInterval(string(key), string(endkey))
	}

	ivs = c.cachedRanges.Stab(ivl)
	for _, iv := range ivs {
		keys := iv.Val.([]string)
		for _, key := range keys {
			c.lru.Remove(key)
		}
	}
	// delete after removing all keys since it is destructive to 'ivs'
	c.cachedRanges.Delete(ivl)
}

// Compact invalidate all caching response before the given rev.
// Replace with the invalidation is lazy. The actual removal happens when the entries is accessed.
func (c *cache) Compact(revision int64) {
	c.mu.Lock()
	defer c.mu.Unlock()

	if revision > c.compactedRev {
		c.compactedRev = revision
	}
}

func (c *cache) Size() int {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.lru.Len()
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Vitess 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 main

import (
	"context"
	"flag"
	"fmt"
	"log/syslog"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"vitess.io/vitess/go/cmd"
	"vitess.io/vitess/go/cmd/vtctldclient/command"
	"vitess.io/vitess/go/exit"
	"vitess.io/vitess/go/trace"
	"vitess.io/vitess/go/vt/log"
	"vitess.io/vitess/go/vt/logutil"
	"vitess.io/vitess/go/vt/servenv"
	"vitess.io/vitess/go/vt/topo"
	"vitess.io/vitess/go/vt/vtctl"
	"vitess.io/vitess/go/vt/vtctl/grpcvtctldserver"
	"vitess.io/vitess/go/vt/vtctl/localvtctldclient"
	"vitess.io/vitess/go/vt/vttablet/tmclient"
	"vitess.io/vitess/go/vt/workflow"
	"vitess.io/vitess/go/vt/wrangler"
)

var (
	waitTime     = flag.Duration("wait-time", 24*time.Hour, "time to wait on an action")
	detachedMode = flag.Bool("detach", false, "detached mode - run vtcl detached from the terminal")
)

func init() {
	logger := logutil.NewConsoleLogger()
	flag.CommandLine.SetOutput(logutil.NewLoggerWriter(logger))
	flag.Usage = func() {
		logger.Printf("Usage: %s [global parameters] command [command parameters]\n", os.Args[0])
		logger.Printf("\nThe global optional parameters are:\n")
		flag.PrintDefaults()
		logger.Printf("\nThe commands are listed below, sorted by group. Use '%s <command> -h' for more help.\n\n", os.Args[0])
		vtctl.PrintAllCommands(logger)
	}
}

// signal handling, centralized here
func installSignalHandlers(cancel func()) {
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
	go func() {
		<-sigChan
		// we got a signal, cancel the current ctx
		cancel()
	}()
}

func main() {
	defer exit.RecoverAll()
	defer logutil.Flush()

	if *detachedMode {
		// this method will call os.Exit and kill this process
		cmd.DetachFromTerminalAndExit()
	}

	args := servenv.ParseFlagsWithArgs("vtctl")
	action := args[0]

	startMsg := fmt.Sprintf("USER=%v SUDO_USER=%v %v", os.Getenv("USER"), os.Getenv("SUDO_USER"), strings.Join(os.Args, " "))

	if syslogger, err := syslog.New(syslog.LOG_INFO, "vtctl "); err == nil {
		syslogger.Info(startMsg) // nolint:errcheck
	} else {
		log.Warningf("cannot connect to syslog: %v", err)
	}

	closer := trace.StartTracing("vtctl")
	defer trace.LogErrorsWhenClosing(closer)

	servenv.FireRunHooks()

	ts := topo.Open()
	defer ts.Close()

	vtctl.WorkflowManager = workflow.NewManager(ts)

	ctx, cancel := context.WithTimeout(context.Background(), *waitTime)
	installSignalHandlers(cancel)

	if strings.EqualFold(action, "LegacyVtctlCommand") {
		args = args[1:]
		if args[0] == "--" {
			args = args[1:]
		}

		action = args[0]
		wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient())
		err := vtctl.RunCommand(ctx, wr, args)
		cancel()
		switch err {
		case vtctl.ErrUnknownCommand:
			flag.Usage()
			exit.Return(1)
		case nil:
			// keep going
		default:
			log.Errorf("action failed: %v %v", action, err)
			exit.Return(255)
		}
	}

	vtctld := grpcvtctldserver.NewVtctldServer(ts)
	localvtctldclient.SetServer(vtctld)
	command.VtctldClientProtocol = "local"
	for i, arg := range args {
		if strings.HasPrefix(arg, "-") {
			if len(arg) == 2 {
				continue
			}

			if strings.HasPrefix(arg, "--") {
				continue
			}

			log.Warningf("Long arg %s begins with a single dash; this will not work in future versions.", arg)
			dashed := strings.ReplaceAll(arg, "_", "-")
			if arg != dashed {
				log.Warningf("Long arg %s is using underscore word separators, switching to dashes. This will not work in future versions.", arg)
				arg = dashed
			}

			arg = "-" + arg
			args[i] = arg
		}
	}

	os.Args = append([]string{"vtctldclient"}, args...)
	if err := command.Root.ExecuteContext(ctx); err != nil {
		log.Errorf("action failed: %v %v", action, err)
		exit.Return(255)
	}
}
<commit_msg>Better backwards compat/migration support<commit_after>/*
Copyright 2019 The Vitess 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 main

import (
	"context"
	"flag"
	"fmt"
	"log/syslog"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"vitess.io/vitess/go/cmd"
	"vitess.io/vitess/go/cmd/vtctldclient/command"
	"vitess.io/vitess/go/exit"
	"vitess.io/vitess/go/trace"
	"vitess.io/vitess/go/vt/log"
	"vitess.io/vitess/go/vt/logutil"
	"vitess.io/vitess/go/vt/servenv"
	"vitess.io/vitess/go/vt/topo"
	"vitess.io/vitess/go/vt/vtctl"
	"vitess.io/vitess/go/vt/vtctl/grpcvtctldserver"
	"vitess.io/vitess/go/vt/vtctl/localvtctldclient"
	"vitess.io/vitess/go/vt/vttablet/tmclient"
	"vitess.io/vitess/go/vt/workflow"
	"vitess.io/vitess/go/vt/wrangler"
)

var (
	waitTime     = flag.Duration("wait-time", 24*time.Hour, "time to wait on an action")
	detachedMode = flag.Bool("detach", false, "detached mode - run vtcl detached from the terminal")
)

func init() {
	logger := logutil.NewConsoleLogger()
	flag.CommandLine.SetOutput(logutil.NewLoggerWriter(logger))
	flag.Usage = func() {
		logger.Printf("Usage: %s [global parameters] command [command parameters]\n", os.Args[0])
		logger.Printf("\nThe global optional parameters are:\n")
		flag.PrintDefaults()
		logger.Printf("\nThe commands are listed below, sorted by group. Use '%s <command> -h' for more help.\n\n", os.Args[0])
		vtctl.PrintAllCommands(logger)
	}
}

// signal handling, centralized here
func installSignalHandlers(cancel func()) {
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
	go func() {
		<-sigChan
		// we got a signal, cancel the current ctx
		cancel()
	}()
}

func main() {
	defer exit.RecoverAll()
	defer logutil.Flush()

	if *detachedMode {
		// this method will call os.Exit and kill this process
		cmd.DetachFromTerminalAndExit()
	}

	args := servenv.ParseFlagsWithArgs("vtctl")
	action := args[0]

	startMsg := fmt.Sprintf("USER=%v SUDO_USER=%v %v", os.Getenv("USER"), os.Getenv("SUDO_USER"), strings.Join(os.Args, " "))

	if syslogger, err := syslog.New(syslog.LOG_INFO, "vtctl "); err == nil {
		syslogger.Info(startMsg) // nolint:errcheck
	} else {
		log.Warningf("cannot connect to syslog: %v", err)
	}

	closer := trace.StartTracing("vtctl")
	defer trace.LogErrorsWhenClosing(closer)

	servenv.FireRunHooks()

	ts := topo.Open()
	defer ts.Close()

	vtctl.WorkflowManager = workflow.NewManager(ts)

	ctx, cancel := context.WithTimeout(context.Background(), *waitTime)
	installSignalHandlers(cancel)

	// (TODO:ajm188) <Begin backwards compatibility support>.
	//
	// For v12, we are going to support new commands by prefixing as:
	//		vtctl VtctldCommand <command> <args...>
	//
	// Existing scripts will continue to use the legacy commands. This is the
	// default case below.
	//
	// We will also support legacy commands by prefixing as:
	//		vtctl LegacyVtctlCommand <command> <args...>
	// This is the fallthrough to the default case.
	//
	// In v13, we will make the default behavior to use the new commands and
	// drop support for the `vtctl VtctldCommand ...` prefix, and legacy
	// commands will only by runnable with the `vtctl LegacyVtctlCommand ...`
	// prefix.
	//
	// In v14, we will drop support for all legacy commands, only running new
	// commands, without any prefixing required or supported.
	switch {
	case strings.EqualFold(action, "VtctldCommand"):
		// New behavior. Strip off the prefix, and set things up to run through
		// the vtctldclient command tree, using the localvtctldclient (in-process)
		// client.
		vtctld := grpcvtctldserver.NewVtctldServer(ts)
		localvtctldclient.SetServer(vtctld)
		command.VtctldClientProtocol = "local"

		os.Args = append([]string{"vtctldclient"}, args[1:]...)
		if err := command.Root.ExecuteContext(ctx); err != nil {
			log.Errorf("action failed: %v %v", action, err)
			exit.Return(255)
		}
	case strings.EqualFold(action, "LegacyVtctlCommand"):
		// Strip off the prefix (being used for compatibility) and fallthrough
		// to the legacy behavior.
		args = args[1:]
		fallthrough
	default:
		if args[0] == "--" {
			args = args[1:]
		}

		action = args[0]
		wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient())
		err := vtctl.RunCommand(ctx, wr, args)
		cancel()
		switch err {
		case vtctl.ErrUnknownCommand:
			flag.Usage()
			exit.Return(1)
		case nil:
			// keep going
		default:
			log.Errorf("action failed: %v %v", action, err)
			exit.Return(255)
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2014 The Cockroach 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. See the AUTHORS file
// for names of contributors.
//
// Author: Spencer Kimball (spencer.kimball@gmail.com)

package gossip

import (
	"fmt"
	"testing"
	"time"

	"github.com/cockroachdb/cockroach/rpc"
	"github.com/cockroachdb/cockroach/util/hlc"
)

// verifyConvergence verifies that info from each node is visible from
// every node in the network within numCycles cycles of the gossip protocol.
func verifyConvergence(numNodes, maxCycles int, t *testing.T) {
	network := NewSimulationNetwork(numNodes, "unix", DefaultTestGossipInterval)

	if connectedCycle := network.RunUntilFullyConnected(); connectedCycle > maxCycles {
		t.Errorf("expected a fully-connected network within %d cycles; took %d",
			maxCycles, connectedCycle)
	}
	network.Stop()
}

// TestConvergence verifies a 10 node gossip network
// converges within 10 cycles.
// TODO(spencer): During race detector tests, it can take >= 8 cycles.
// Figure out a more deterministic setup.
func TestConvergence(t *testing.T) {
	verifyConvergence(10, 10, t)
}

// TestGossipInfoStore verifies operation of gossip instance infostore.
func TestGossipInfoStore(t *testing.T) {
	rpcContext := rpc.NewContext(hlc.NewClock(hlc.UnixNano), rpc.LoadInsecureTLSConfig())
	g := New(rpcContext)
	g.AddInfo("i", int64(1), time.Hour)
	if val, err := g.GetInfo("i"); val.(int64) != int64(1) || err != nil {
		t.Errorf("error fetching int64: %v", err)
	}
	if _, err := g.GetInfo("i2"); err == nil {
		t.Errorf("expected error fetching nonexistent key \"i2\"")
	}
	g.AddInfo("f", float64(3.14), time.Hour)
	if val, err := g.GetInfo("f"); val.(float64) != float64(3.14) || err != nil {
		t.Errorf("error fetching float64: %v", err)
	}
	if _, err := g.GetInfo("f2"); err == nil {
		t.Errorf("expected error fetching nonexistent key \"f2\"")
	}
	g.AddInfo("s", "b", time.Hour)
	if val, err := g.GetInfo("s"); val.(string) != "b" || err != nil {
		t.Errorf("error fetching string: %v", err)
	}
	if _, err := g.GetInfo("s2"); err == nil {
		t.Errorf("expected error fetching nonexistent key \"s2\"")
	}
}

// TestGossipGroupsInfoStore verifies gossiping of groups via the
// gossip instance infostore.
func TestGossipGroupsInfoStore(t *testing.T) {
	rpcContext := rpc.NewContext(hlc.NewClock(hlc.UnixNano), rpc.LoadInsecureTLSConfig())
	g := New(rpcContext)

	// For int64.
	g.RegisterGroup("i", 3, MinGroup)
	for i := 0; i < 3; i++ {
		g.AddInfo(fmt.Sprintf("i.%d", i), int64(i), time.Hour)
	}
	values, err := g.GetGroupInfos("i")
	if err != nil {
		t.Errorf("error fetching int64 group: %v", err)
	}
	if len(values) != 3 {
		t.Errorf("incorrect number of values in group: %v", values)
	}
	for i := 0; i < 3; i++ {
		if values[i].(int64) != int64(i) {
			t.Errorf("index %d has incorrect value: %d, expected %d", i, values[i].(int64), i)
		}
	}
	if _, err := g.GetGroupInfos("i2"); err == nil {
		t.Errorf("expected error fetching nonexistent key \"i2\"")
	}

	// For float64.
	g.RegisterGroup("f", 3, MinGroup)
	for i := 0; i < 3; i++ {
		g.AddInfo(fmt.Sprintf("f.%d", i), float64(i), time.Hour)
	}
	values, err = g.GetGroupInfos("f")
	if err != nil {
		t.Errorf("error fetching float64 group: %v", err)
	}
	if len(values) != 3 {
		t.Errorf("incorrect number of values in group: %v", values)
	}
	for i := 0; i < 3; i++ {
		if values[i].(float64) != float64(i) {
			t.Errorf("index %d has incorrect value: %f, expected %d", i, values[i].(float64), i)
		}
	}

	// For string.
	g.RegisterGroup("s", 3, MinGroup)
	for i := 0; i < 3; i++ {
		g.AddInfo(fmt.Sprintf("s.%d", i), fmt.Sprintf("%d", i), time.Hour)
	}
	values, err = g.GetGroupInfos("s")
	if err != nil {
		t.Errorf("error fetching string group: %v", err)
	}
	if len(values) != 3 {
		t.Errorf("incorrect number of values in group: %v", values)
	}
	for i := 0; i < 3; i++ {
		if values[i].(string) != fmt.Sprintf("%d", i) {
			t.Errorf("index %d has incorrect value: %d, expected %s", i, values[i], fmt.Sprintf("%d", i))
		}
	}
}
<commit_msg>allow up to 15 cycles in gossip test<commit_after>// Copyright 2014 The Cockroach 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. See the AUTHORS file
// for names of contributors.
//
// Author: Spencer Kimball (spencer.kimball@gmail.com)

package gossip

import (
	"fmt"
	"testing"
	"time"

	"github.com/cockroachdb/cockroach/rpc"
	"github.com/cockroachdb/cockroach/util/hlc"
)

// verifyConvergence verifies that info from each node is visible from
// every node in the network within numCycles cycles of the gossip protocol.
// TODO(Tobias): Currently this test is nondeterministic, usually requiring
// more cycles for a fully connected network on busy hardware. The clock
// should be advanced manually instead (this requires some changes to gossip).
func verifyConvergence(numNodes, maxCycles int, t *testing.T) {
	network := NewSimulationNetwork(numNodes, "unix", DefaultTestGossipInterval)

	if connectedCycle := network.RunUntilFullyConnected(); connectedCycle > maxCycles {
		t.Errorf("expected a fully-connected network within %d cycles; took %d",
			maxCycles, connectedCycle)
	}
	network.Stop()
}

// TestConvergence verifies a 10 node gossip network
// converges within 10 cycles.
// TODO(spencer): During race detector tests, it can take >= 8 cycles.
// Figure out a more deterministic setup.
func TestConvergence(t *testing.T) {
	// 15 cycles to accommodate slower hardware. Usually 10 should do.
	verifyConvergence(10, 15, t)
}

// TestGossipInfoStore verifies operation of gossip instance infostore.
func TestGossipInfoStore(t *testing.T) {
	rpcContext := rpc.NewContext(hlc.NewClock(hlc.UnixNano), rpc.LoadInsecureTLSConfig())
	g := New(rpcContext)
	g.AddInfo("i", int64(1), time.Hour)
	if val, err := g.GetInfo("i"); val.(int64) != int64(1) || err != nil {
		t.Errorf("error fetching int64: %v", err)
	}
	if _, err := g.GetInfo("i2"); err == nil {
		t.Errorf("expected error fetching nonexistent key \"i2\"")
	}
	g.AddInfo("f", float64(3.14), time.Hour)
	if val, err := g.GetInfo("f"); val.(float64) != float64(3.14) || err != nil {
		t.Errorf("error fetching float64: %v", err)
	}
	if _, err := g.GetInfo("f2"); err == nil {
		t.Errorf("expected error fetching nonexistent key \"f2\"")
	}
	g.AddInfo("s", "b", time.Hour)
	if val, err := g.GetInfo("s"); val.(string) != "b" || err != nil {
		t.Errorf("error fetching string: %v", err)
	}
	if _, err := g.GetInfo("s2"); err == nil {
		t.Errorf("expected error fetching nonexistent key \"s2\"")
	}
}

// TestGossipGroupsInfoStore verifies gossiping of groups via the
// gossip instance infostore.
func TestGossipGroupsInfoStore(t *testing.T) {
	rpcContext := rpc.NewContext(hlc.NewClock(hlc.UnixNano), rpc.LoadInsecureTLSConfig())
	g := New(rpcContext)

	// For int64.
	g.RegisterGroup("i", 3, MinGroup)
	for i := 0; i < 3; i++ {
		g.AddInfo(fmt.Sprintf("i.%d", i), int64(i), time.Hour)
	}
	values, err := g.GetGroupInfos("i")
	if err != nil {
		t.Errorf("error fetching int64 group: %v", err)
	}
	if len(values) != 3 {
		t.Errorf("incorrect number of values in group: %v", values)
	}
	for i := 0; i < 3; i++ {
		if values[i].(int64) != int64(i) {
			t.Errorf("index %d has incorrect value: %d, expected %d", i, values[i].(int64), i)
		}
	}
	if _, err := g.GetGroupInfos("i2"); err == nil {
		t.Errorf("expected error fetching nonexistent key \"i2\"")
	}

	// For float64.
	g.RegisterGroup("f", 3, MinGroup)
	for i := 0; i < 3; i++ {
		g.AddInfo(fmt.Sprintf("f.%d", i), float64(i), time.Hour)
	}
	values, err = g.GetGroupInfos("f")
	if err != nil {
		t.Errorf("error fetching float64 group: %v", err)
	}
	if len(values) != 3 {
		t.Errorf("incorrect number of values in group: %v", values)
	}
	for i := 0; i < 3; i++ {
		if values[i].(float64) != float64(i) {
			t.Errorf("index %d has incorrect value: %f, expected %d", i, values[i].(float64), i)
		}
	}

	// For string.
	g.RegisterGroup("s", 3, MinGroup)
	for i := 0; i < 3; i++ {
		g.AddInfo(fmt.Sprintf("s.%d", i), fmt.Sprintf("%d", i), time.Hour)
	}
	values, err = g.GetGroupInfos("s")
	if err != nil {
		t.Errorf("error fetching string group: %v", err)
	}
	if len(values) != 3 {
		t.Errorf("incorrect number of values in group: %v", values)
	}
	for i := 0; i < 3; i++ {
		if values[i].(string) != fmt.Sprintf("%d", i) {
			t.Errorf("index %d has incorrect value: %d, expected %s", i, values[i], fmt.Sprintf("%d", i))
		}
	}
}
<|endoftext|>