<commit_before>package exmail

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"

	"lcgc/platform/staffio/models"
)

var (
	errEmptyAuths = errors.New("empty auths")
	api_auths     string
)

const (
	url_token    = "https://exmail.qq.com/cgi-bin/token"
	url_user_get = "http://openapi.exmail.qq.com:12211/openapi/user/get"
)

func init() {
	api_auths = os.Getenv("EXMAIL_API_AUTHS")
}

type OpenType uint8

const (
	OTIgnore   OpenType = 0
	OTEnabled  OpenType = 1
	OTDisabled OpenType = 2
)

func GetStaff(email string) (*models.Staff, error) {
	user, err := requestUserGet(email)
	if err != nil {
		return nil, err
	}

	sn, gn := models.SplitName(user.Name)
	log.Printf("%q %q %q", user.Name, sn, gn)

	return &models.Staff{
		Uid:            strings.Split(user.Alias, "@")[0],
		Email:          user.Alias,
		CommonName:     user.Name,
		Surname:        sn,
		GivenName:      gn,
		EmployeeNumber: user.ExtId,
		EmployeeType:   user.Title,
		Mobile:         user.Mobile,
		Gender:         user.Gender,
	}, nil
}

/*{
"Alias": " test2@gzservice.com",
"Name": "鲍勃",
"Gender": 1,
"SlaveList": "bb@gzdev.com,bo@gzdev.com",
"Position": "工程师",
"Tel": "62394",
"Mobile": "",
"ExtId": "100",
"PartyList": {
	"Count": 3,
	"List": [{ "Value":"部门 a" }
		,{ "Value":"部门 B/部门 b" }
		,{"Value":"部门 c" }
}}*/
type userResp struct {
	Alias    string        `json:"Alias"`
	Name     string        `json:"Name"`
	Aliases  string        `json:"SlaveList"`
	Gender   models.Gender `json:"Gender"`
	Title    string        `json:"Position"`
	ExtId    string        `json:"ExtId"`
	Tel      string        `json:"Tel"`
	Mobile   string        `json:"Mobile"`
	OpenType OpenType      `json:"OpenType"`
}

type apiError struct {
	Arg     string `json:"arg"`
	ErrCode string `json:"errcode"`
	ErrMsg  string `json:"error"`
}

func (e *apiError) Error() string {
	return fmt.Sprintf("%s:%s %q", e.ErrCode, e.ErrMsg, e.Arg)
}

func requestUserGet(alias string) (*userResp, error) {
	auths := "Bearer " + requestAccessToken()
	resp, err := doHTTP("POST", url_user_get, auths, bytes.NewBufferString("alias="+alias))
	if err != nil {
		log.Printf("doHTTP err %s", err)
		return nil, err
	}

	log.Printf("resp: %s", resp)

	exErr := &apiError{Arg: alias}
	if e := json.Unmarshal(resp, exErr); e != nil {
		log.Printf("unmarshal api err %s", e)
		return nil, e
	}

	if exErr.ErrCode != "" {
		log.Printf("apiError %s", exErr)
		return nil, exErr
	}

	obj := &userResp{}
	if e := json.Unmarshal(resp, obj); e != nil {
		log.Printf("unmarshal user err %s", e)
		return nil, e
	}

	return obj, nil
}

type tokenResp struct {
	AccessToken  string `json:"access_token"`
	Type         string `json:"token_type"`
	ExpiresIn    int    `json:"expires_in"`
	RefreshToken string `json:"refresh_token"`
}

func requestAccessToken() (token string) {
	if api_auths == "" {
		log.Fatal(errEmptyAuths)
	}
	auths := "Basic " + api_auths
	// log.Printf("auths: %s", auths)
	body_str := "grant_type=client_credentials"
	resp, err := doHTTP("POST", url_token, auths, bytes.NewBufferString(body_str))
	if err != nil {
		log.Printf("doHTTP err %s", err)
		return
	}

	// log.Printf("%s", resp)
	obj := &tokenResp{}
	if e := json.Unmarshal(resp, obj); e != nil {
		log.Printf("unmarshal err %s", e)
		return
	}

	token = obj.AccessToken
	return
}

func doHTTP(method, url string, auths string, body io.Reader) ([]byte, error) {

	// log.Printf("doHTTP: %s %s", method, url)

	req, e := http.NewRequest(method, url, body)
	if e != nil {
		log.Println(e, method, url)
		return nil, e
	}

	if method == "POST" {
		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	}

	if auths != "" {
		req.Header.Set("Authorization", auths)
	}

	c := &http.Client{}
	resp, e := c.Do(req)
	if e != nil {
		return nil, e
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 300 || resp.StatusCode < 200 {
		log.Printf("http code error %d, %s", resp.StatusCode, resp.Status)
		return nil, fmt.Errorf("Expecting HTTP status code 20x, but got %v", resp.StatusCode)
	}

	rbody, e := ioutil.ReadAll(resp.Body)
	if e != nil {
		return nil, e
	}

	return rbody, nil
}
<commit_msg>fix empty auths<commit_after>package exmail

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"

	"lcgc/platform/staffio/models"
)

var (
	errEmptyAuths = errors.New("empty auths")
	api_auths     string
)

const (
	url_token    = "https://exmail.qq.com/cgi-bin/token"
	url_user_get = "http://openapi.exmail.qq.com:12211/openapi/user/get"
)

func init() {
	api_auths = os.Getenv("EXMAIL_API_AUTHS")
}

type OpenType uint8

const (
	OTIgnore   OpenType = 0
	OTEnabled  OpenType = 1
	OTDisabled OpenType = 2
)

func GetStaff(email string) (*models.Staff, error) {
	user, err := requestUserGet(email)
	if err != nil {
		return nil, err
	}

	sn, gn := models.SplitName(user.Name)
	log.Printf("%q %q %q", user.Name, sn, gn)

	return &models.Staff{
		Uid:            strings.Split(user.Alias, "@")[0],
		Email:          user.Alias,
		CommonName:     user.Name,
		Surname:        sn,
		GivenName:      gn,
		EmployeeNumber: user.ExtId,
		EmployeeType:   user.Title,
		Mobile:         user.Mobile,
		Gender:         user.Gender,
	}, nil
}

/*{
"Alias": " test2@gzservice.com",
"Name": "鲍勃",
"Gender": 1,
"SlaveList": "bb@gzdev.com,bo@gzdev.com",
"Position": "工程师",
"Tel": "62394",
"Mobile": "",
"ExtId": "100",
"PartyList": {
	"Count": 3,
	"List": [{ "Value":"部门 a" }
		,{ "Value":"部门 B/部门 b" }
		,{"Value":"部门 c" }
}}*/
type userResp struct {
	Alias    string        `json:"Alias"`
	Name     string        `json:"Name"`
	Aliases  string        `json:"SlaveList"`
	Gender   models.Gender `json:"Gender"`
	Title    string        `json:"Position"`
	ExtId    string        `json:"ExtId"`
	Tel      string        `json:"Tel"`
	Mobile   string        `json:"Mobile"`
	OpenType OpenType      `json:"OpenType"`
}

type apiError struct {
	Arg     string `json:"arg"`
	ErrCode string `json:"errcode"`
	ErrMsg  string `json:"error"`
}

func (e *apiError) Error() string {
	return fmt.Sprintf("%s:%s %q", e.ErrCode, e.ErrMsg, e.Arg)
}

func requestUserGet(alias string) (*userResp, error) {
	token, err := requestAccessToken()
	if err != nil {
		return nil, err
	}
	auths := "Bearer " + token
	resp, err := doHTTP("POST", url_user_get, auths, bytes.NewBufferString("alias="+alias))
	if err != nil {
		log.Printf("doHTTP err %s", err)
		return nil, err
	}

	log.Printf("resp: %s", resp)

	exErr := &apiError{Arg: alias}
	if e := json.Unmarshal(resp, exErr); e != nil {
		log.Printf("unmarshal api err %s", e)
		return nil, e
	}

	if exErr.ErrCode != "" {
		log.Printf("apiError %s", exErr)
		return nil, exErr
	}

	obj := &userResp{}
	if e := json.Unmarshal(resp, obj); e != nil {
		log.Printf("unmarshal user err %s", e)
		return nil, e
	}

	return obj, nil
}

type tokenResp struct {
	AccessToken  string `json:"access_token"`
	Type         string `json:"token_type"`
	ExpiresIn    int    `json:"expires_in"`
	RefreshToken string `json:"refresh_token"`
}

func requestAccessToken() (token string, err error) {
	if api_auths == "" {
		err = errEmptyAuths
		log.Print(err)
		return
	}
	auths := "Basic " + api_auths
	// log.Printf("auths: %s", auths)
	body_str := "grant_type=client_credentials"
	resp, err := doHTTP("POST", url_token, auths, bytes.NewBufferString(body_str))
	if err != nil {
		log.Printf("doHTTP err %s", err)
		return
	}

	// log.Printf("%s", resp)
	obj := &tokenResp{}
	if e := json.Unmarshal(resp, obj); e != nil {
		log.Printf("unmarshal err %s", e)
		return
	}

	token = obj.AccessToken
	return
}

func doHTTP(method, url string, auths string, body io.Reader) ([]byte, error) {

	// log.Printf("doHTTP: %s %s", method, url)

	req, e := http.NewRequest(method, url, body)
	if e != nil {
		log.Println(e, method, url)
		return nil, e
	}

	if method == "POST" {
		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	}

	if auths != "" {
		req.Header.Set("Authorization", auths)
	}

	c := &http.Client{}
	resp, e := c.Do(req)
	if e != nil {
		return nil, e
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 300 || resp.StatusCode < 200 {
		log.Printf("http code error %d, %s", resp.StatusCode, resp.Status)
		return nil, fmt.Errorf("Expecting HTTP status code 20x, but got %v", resp.StatusCode)
	}

	rbody, e := ioutil.ReadAll(resp.Body)
	if e != nil {
		return nil, e
	}

	return rbody, nil
}
<|endoftext|>


<commit_before>package main

import (
	"math/rand"
	"sync"
	"time"
)

const encodeURL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"

var (
	nano      = int64(0)
	nanoMutex = &sync.Mutex{}
)

func generateUUID() string {
	newNano := time.Now().UnixNano()

	// Avoid duplicate nanoseconds
	nanoMutex.Lock()
	if newNano <= nano {
		newNano = nano + 1
	}
	nano = newNano
	nanoMutex.Unlock()

	src := rand.NewSource(newNano)
	r := rand.New(src)

	uuid := make([]byte, 16)
	buffer := make([]byte, 23)
	nodeAddress := make([]byte, 6)

	clockSequence := r.Intn(16384)
	uuid[8] = byte(((clockSequence >> 8) & 0x3F) | 0x80)
	uuid[9] = byte(clockSequence & 0xFF)

	for i := 0; i < 6; i++ {
		nodeAddress[i] = byte(r.Intn(256))
	}

	nodeAddress[0] |= 0x80

	for i := 0; i < 6; i++ {
		uuid[i+10] = nodeAddress[i]
	}

	// Adjustment between Unix epoch and September 15, 1582
	epoch_adjustment := time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC).Unix() - time.Date(1582, time.September, 15, 0, 0, 0, 0, time.UTC).Unix()

	currentTime := newNano + epoch_adjustment
	currentTime *= 10000
	currentTime |= 0x1000000000000000

	for i := 0; i < 4; i++ {
		uuid[i] = byte(currentTime >> uint(8*(3-i)) & 0xFF)
	}

	for i := 0; i < 2; i++ {
		uuid[i+4] = byte(currentTime >> uint(8*(1-i)+32) & 0xFF)
	}

	for i := 0; i < 2; i++ {
		uuid[i+6] = byte(currentTime >> uint(8*(1-i)+48) & 0xFF)
	}

	buffer[0] = '_'

	for i := 0; i < 5; i++ {
		buffer[4*i+1] = encodeURL[(uuid[i*3]>>2)&0x3F]
		buffer[4*i+2] = encodeURL[((uuid[i*3]<<4)&0x30)|((uuid[i*3+1]>>4)&0xF)]
		buffer[4*i+3] = encodeURL[((uuid[i*3+1]<<2)&0x3C)|((uuid[i*3+2]>>6)&0x3)]
		buffer[4*i+4] = encodeURL[uuid[i*3+2]&0x3F]
	}

	buffer[21] = encodeURL[(uuid[15]>>2)&0x3F]
	buffer[22] = encodeURL[(uuid[15]<<4)&0x30]

	return string(buffer)
}
<commit_msg>Fix the random seed for the UUID generator.<commit_after>package main

import (
	cr "crypto/rand"
	"math/rand"
	"sync"
	"time"
)

const encodeURL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"

var (
	nano      = int64(0)
	nanoMutex = &sync.Mutex{}

	// Random number generator seed
	seed = int64(0)
)

func init() {
	// Generate a random seed
	buf := make([]byte, 8)
	_, err := cr.Read(buf)
	if err != nil {
		panic(err)
	}

	for i, b := range buf {
		seed |= int64(b) << uint((7-i)*8)
	}
}

func generateUUID() string {
	newNano := time.Now().UnixNano()

	// Avoid duplicate nanoseconds
	nanoMutex.Lock()
	if newNano <= nano {
		newNano = nano + 1
	}
	nano = newNano
	nanoMutex.Unlock()

	src := rand.NewSource(seed)
	r := rand.New(src)

	uuid := make([]byte, 16)
	buffer := make([]byte, 23)
	nodeAddress := make([]byte, 6)

	clockSequence := r.Intn(16384)
	uuid[8] = byte(((clockSequence >> 8) & 0x3F) | 0x80)
	uuid[9] = byte(clockSequence & 0xFF)

	for i := 0; i < 6; i++ {
		nodeAddress[i] = byte(r.Intn(256))
	}

	nodeAddress[0] |= 0x80

	for i := 0; i < 6; i++ {
		uuid[i+10] = nodeAddress[i]
	}

	// Adjustment between Unix epoch and September 15, 1582
	epoch_adjustment := time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC).Unix() - time.Date(1582, time.September, 15, 0, 0, 0, 0, time.UTC).Unix()

	currentTime := newNano + epoch_adjustment
	currentTime *= 10000
	currentTime |= 0x1000000000000000

	for i := 0; i < 4; i++ {
		uuid[i] = byte(currentTime >> uint(8*(3-i)) & 0xFF)
	}

	for i := 0; i < 2; i++ {
		uuid[i+4] = byte(currentTime >> uint(8*(1-i)+32) & 0xFF)
	}

	for i := 0; i < 2; i++ {
		uuid[i+6] = byte(currentTime >> uint(8*(1-i)+48) & 0xFF)
	}

	buffer[0] = '_'

	for i := 0; i < 5; i++ {
		buffer[4*i+1] = encodeURL[(uuid[i*3]>>2)&0x3F]
		buffer[4*i+2] = encodeURL[((uuid[i*3]<<4)&0x30)|((uuid[i*3+1]>>4)&0xF)]
		buffer[4*i+3] = encodeURL[((uuid[i*3+1]<<2)&0x3C)|((uuid[i*3+2]>>6)&0x3)]
		buffer[4*i+4] = encodeURL[uuid[i*3+2]&0x3F]
	}

	buffer[21] = encodeURL[(uuid[15]>>2)&0x3F]
	buffer[22] = encodeURL[(uuid[15]<<4)&0x30]

	return string(buffer)
}
<|endoftext|>


<commit_before>package geometry

import (
	"fmt"
	"math"
)

type Line2D struct {
	P1, P2 Point2D
}

func (l Line2D) Angle() float64 {
	dx, dy := l.P2.X-l.P1.X, l.P2.Y-l.P1.Y
	return math.Atan(dy / dx)
}

// From Dan Sunday,
// http://softsurfer.com/Archive/algorithm_0102/algorithm_0102.htm
func (l Line2D) DistanceToPoint(p Point2D, segment bool) float64 {
	v := l.ToVector()
	w := p.Minus(l.P1)
	c1 := DotProduct2D(w, v)
	if segment && c1 <= 0 {
		return p.DistanceTo(l.P1)
	}
	c2 := DotProduct2D(v, v)
	if segment && c2 <= c1 {
		return p.DistanceTo(l.P2)
	}
	b := c1 / c2
	a := l.P1.Plus(v.Scaled(b))
	return p.DistanceTo(a)
}

// Returns the intersection point and if said point occures on both lines.
// From Graphics Gems III, Faster Line Segment Intersection.
func (l1 Line2D) Intersection(l2 Line2D) (Point2D, bool) {
	a := l1.P2.Minus(l1.P1)
	b := l2.P1.Minus(l2.P2)
	denominator := a.Y*b.X - a.X*b.Y
	if denominator == 0.0 {
		return Point2D{math.Inf(1), math.Inf(1)}, false
	}
	denominator = 1.0 / denominator
	c := l1.P1.Minus(l2.P1)
	A := (b.Y*c.X - b.X*c.Y) * denominator
	intersection := l1.P1.Plus(a.Scaled(A))
	if A < 0.0 || A > 1.0 {
		return intersection, false
	}
	B := (a.X*c.Y - a.Y*c.X) * denominator
	if B < 0.0 || B > 1.0 {
		return intersection, false
	}
	return intersection, true
}

// From Graphics Gems III, Faster Line Segment Intersection.
func (l1 Line2D) Intersects(l2 Line2D) bool {
	a := l1.P2.Minus(l1.P1)
	b := l2.P1.Minus(l2.P2)
	denominator := a.Y*b.X - a.X*b.Y
	if denominator == 0.0 {
		return false
	}
	c := l1.P1.Minus(l2.P1)
	A := b.Y*c.X - b.X*c.Y
	if denominator > 0 && (A < 0.0 || A > denominator) {
		return false
	}
	B := a.X*c.Y - a.Y*c.X
	if denominator > 0 && (B > 0.0 || B < denominator) {
		return false
	}
	return true
}

func (l Line2D) Length() float64 {
	dx, dy := l.P2.X-l.P1.X, l.P2.Y-l.P1.Y
	return math.Sqrt(dx*dx + dy*dy)
}

func (l Line2D) String() string {
	return fmt.Sprintf("{%v %v}", l.P1, l.P2)
}

func (l Line2D) ToVector() Point2D {
	return Point2D{l.P2.X - l.P1.X, l.P2.Y - l.P1.Y}
}
<commit_msg>Testing some line differences.<commit_after>package geometry

import (
	"fmt"
	"math"
)

type Line2D struct {
	P1, P2 Point2D
}

func (l Line2D) Angle() float64 {
	dx, dy := l.P2.X-l.P1.X, l.P2.Y-l.P1.Y
	return math.Atan(dy / dx)
}

// Angular difference between the line l and a line with endpoints at line l's
// midpoint and point p.
func (l Line2D) AngularDistanceToPoint(p Point2D) float64 {
	rl := Line2D{l.Midpoint(), p}
	return rl.Angle() - l.Angle()
}

// From Dan Sunday,
// http://softsurfer.com/Archive/algorithm_0102/algorithm_0102.htm
func (l Line2D) DistanceToPoint(p Point2D, segment bool) float64 {
	v := l.ToVector()
	w := p.Minus(l.P1)
	c1 := DotProduct2D(w, v)
	if segment && c1 <= 0 {
		return p.DistanceTo(l.P1)
	}
	c2 := DotProduct2D(v, v)
	if segment && c2 <= c1 {
		return p.DistanceTo(l.P2)
	}
	b := c1 / c2
	a := l.P1.Plus(v.Scaled(b))
	return p.DistanceTo(a)
}

// Distance of (either) end point of line l to a line with endpoints at line
// l's midpoint and point p.
func (l Line2D) EndpointDistanceToPoint(p Point2D) float64 {
	rl := Line2D{l.Midpoint(), p}
	return rl.DistanceToPoint(l.P1, false)
}

func (l Line2D) EndpointDistanceToPoint2(p Point2D) float64 {
	mp := l.Midpoint()
	t := Line2D{mp, p}.Angle() - l.Angle()
	a := Line2D{mp, l.P1}.Length()
	return a * math.Tan(t)
}

// Returns the intersection point and if said point occures on both lines.
// From Graphics Gems III, Faster Line Segment Intersection.
func (l1 Line2D) Intersection(l2 Line2D) (Point2D, bool) {
	a := l1.P2.Minus(l1.P1)
	b := l2.P1.Minus(l2.P2)
	denominator := a.Y*b.X - a.X*b.Y
	if denominator == 0.0 {
		return Point2D{math.Inf(1), math.Inf(1)}, false
	}
	denominator = 1.0 / denominator
	c := l1.P1.Minus(l2.P1)
	A := (b.Y*c.X - b.X*c.Y) * denominator
	intersection := l1.P1.Plus(a.Scaled(A))
	if A < 0.0 || A > 1.0 {
		return intersection, false
	}
	B := (a.X*c.Y - a.Y*c.X) * denominator
	if B < 0.0 || B > 1.0 {
		return intersection, false
	}
	return intersection, true
}

// From Graphics Gems III, Faster Line Segment Intersection.
func (l1 Line2D) Intersects(l2 Line2D) bool {
	a := l1.P2.Minus(l1.P1)
	b := l2.P1.Minus(l2.P2)
	denominator := a.Y*b.X - a.X*b.Y
	if denominator == 0.0 {
		return false
	}
	c := l1.P1.Minus(l2.P1)
	A := b.Y*c.X - b.X*c.Y
	if denominator > 0 && (A < 0.0 || A > denominator) {
		return false
	}
	B := a.X*c.Y - a.Y*c.X
	if denominator > 0 && (B > 0.0 || B < denominator) {
		return false
	}
	return true
}

func (l Line2D) Length() float64 {
	dx, dy := l.P2.X-l.P1.X, l.P2.Y-l.P1.Y
	return math.Sqrt(dx*dx + dy*dy)
}

func (l Line2D) Midpoint() Point2D {
	return Point2D{(l.P1.X + l.P2.X) * 0.5, (l.P1.Y + l.P2.Y) * 0.5}
}

func (l Line2D) String() string {
	return fmt.Sprintf("{%v %v}", l.P1, l.P2)
}

func (l Line2D) ToVector() Point2D {
	return Point2D{l.P2.X - l.P1.X, l.P2.Y - l.P1.Y}
}
<|endoftext|>


<commit_before>/*
Copyright 2018 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 merge_test

import (
	"testing"

	"sigs.k8s.io/structured-merge-diff/merge"
	"sigs.k8s.io/structured-merge-diff/typed"
)

// State of the current test in terms of live object. One can check at
// any time that Live and Owners match the expectations.
type State struct {
	Live    typed.TypedValue
	Owners  merge.Owners
	Updater *merge.Updater
}

// Update the current state with the passed in object
func (s *State) Update(obj typed.TypedValue, owner string) error {
	if s.Owners == nil {
		s.Owners = merge.Owners{}
	}
	owners, err := s.Updater.Update(s.Live, obj, s.Owners, owner)
	if err != nil {
		return err
	}
	s.Live = obj
	s.Owners = owners

	return nil
}

// Apply the passed in object to the current state
func (s *State) Apply(obj typed.TypedValue, owner string, force bool) error {
	if s.Owners == nil {
		s.Owners = merge.Owners{}
	}
	new, owners, err := s.Updater.Apply(s.Live, obj, s.Owners, owner, force)
	if err != nil {
		return err
	}
	s.Live = new
	s.Owners = owners

	return nil
}

// TestExample shows how to use the test framework
func TestExample(t *testing.T) {
	state := &State{Updater: &merge.Updater{}}
	parser, err := typed.NewParser(`types:
- name: lists
  struct:
    fields:
    - name: list
      type:
        list:
          elementType:
            scalar: string
          elementRelationship: associative`)
	if err != nil {
		t.Fatalf("Failed to create parser: %v", err)
	}

	config, err := parser.FromYAML(`
list:
- a
- b
- c
`, "lists")
	if err != nil {
		t.Fatalf("Failed to parse YAML: %v", err)
	}
	err = state.Apply(config, "default", false)
	if err != nil {
		t.Fatalf("Wanted err = %v, got %v", nil, err)
	}

	config, err = parser.FromYAML(`
list:
- a
- b
- c
- d`, "lists")
	if err != nil {
		t.Fatalf("Failed to parse YAML: %v", err)
	}
	err = state.Apply(config, "default", false)

	if err != nil {
		t.Fatalf("Wanted err = %v, got %v", nil, err)
	}

	// The following is wrong because the code doesn't work yet.
	_, err = state.Live.Compare(config)
	if err == nil {
		t.Fatalf("Succeeded to compare live with config")
	}
}
<commit_msg>Parse objects in state, create empty when needed<commit_after>/*
Copyright 2018 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 merge_test

import (
	"fmt"
	"testing"

	"sigs.k8s.io/structured-merge-diff/merge"
	"sigs.k8s.io/structured-merge-diff/typed"
)

// State of the current test in terms of live object. One can check at
// any time that Live and Owners match the expectations.
type State struct {
	Live   *typed.TypedValue
	Parser *typed.Parser
	// Typename is the typename used to create objects in the
	// schema.
	Typename string
	Owners   merge.Owners
	Updater  *merge.Updater
}

func (s *State) checkInit() error {
	if s.Owners == nil {
		s.Owners = merge.Owners{}
	}
	if s.Live == nil {
		obj, err := s.Parser.NewEmpty(s.Typename)
		if err != nil {
			return fmt.Errorf("failed to create new empty object: %v", err)
		}
		s.Live = &obj
	}
	return nil
}

// Update the current state with the passed in object
func (s *State) Update(obj typed.YAMLObject, owner string) error {
	if err := s.checkInit(); err != nil {
		return err
	}
	tv, err := s.Parser.FromYAML(obj, s.Typename)
	owners, err := s.Updater.Update(*s.Live, tv, s.Owners, owner)
	if err != nil {
		return err
	}
	s.Live = &tv
	s.Owners = owners

	return nil
}

// Apply the passed in object to the current state
func (s *State) Apply(obj typed.YAMLObject, owner string, force bool) error {
	if err := s.checkInit(); err != nil {
		return err
	}
	tv, err := s.Parser.FromYAML(obj, s.Typename)
	if err != nil {
		return err
	}
	new, owners, err := s.Updater.Apply(*s.Live, tv, s.Owners, owner, force)
	if err != nil {
		return err
	}
	s.Live = &new
	s.Owners = owners

	return nil
}

// CompareLive takes a YAML string and returns the comparison with the
// current live object or an error.
func (s *State) CompareLive(obj typed.YAMLObject) (*typed.Comparison, error) {
	if err := s.checkInit(); err != nil {
		return nil, err
	}
	tv, err := s.Parser.FromYAML(obj, s.Typename)
	if err != nil {
		return nil, err
	}
	return s.Live.Compare(tv)
}

// TestExample shows how to use the test framework
func TestExample(t *testing.T) {
	parser, err := typed.NewParser(`types:
- name: lists
  struct:
    fields:
    - name: list
      type:
        list:
          elementType:
            scalar: string
          elementRelationship: associative`)
	if err != nil {
		t.Fatalf("Failed to create parser: %v", err)
	}
	state := &State{
		Updater:  &merge.Updater{},
		Parser:   parser,
		Typename: "lists",
	}

	config := typed.YAMLObject(`
list:
- a
- b
- c
`)
	err = state.Apply(config, "default", false)
	if err != nil {
		t.Fatalf("Wanted err = %v, got %v", nil, err)
	}

	config = typed.YAMLObject(`
list:
- a
- b
- c
- d`)
	err = state.Apply(config, "default", false)

	if err != nil {
		t.Fatalf("Wanted err = %v, got %v", nil, err)
	}

	// The following is wrong because the code doesn't work yet.
	_, err = state.CompareLive(config)
	if err == nil {
		t.Fatalf("Succeeded to compare live with config")
	}
}
<|endoftext|>


<commit_before>// Copyright 2017 Stratumn SAS. All rights reserved.
//
// 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 couchstore

import (
	"flag"
	"testing"

	"github.com/stratumn/sdk/store"

	"github.com/stratumn/sdk/store/storetestcases"
)

var (
	myCouchstore *CouchStore
	test         *testing.T
	integration  = flag.Bool("integration", false, "Run integration tests")
)

func TestCouchStore(t *testing.T) {
	flag.Parse()
	test = t
	if *integration {
		storetestcases.Factory{
			New:  newTestCouchStore,
			Free: freeTestCouchStore,
		}.RunTests(t)
	}
}

func newTestCouchStore() (store.Adapter, error) {
	var err error
	config := &Config{
		Address: "http://localhost:5984",
	}
	myCouchstore, err = New(config)
	if err != nil {
		test.Logf(err.Error())
		test.FailNow()
	}
	return myCouchstore, nil
}

func freeTestCouchStore(a store.Adapter) {
	if err := myCouchstore.deleteDatabase(dbSegment); err != nil {
		test.Fatal(err)
	}
}
<commit_msg>couchstore: clean adapter init in test (#243)<commit_after>// Copyright 2017 Stratumn SAS. All rights reserved.
//
// 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 couchstore

import (
	"flag"
	"testing"

	"github.com/stratumn/sdk/store"

	"github.com/stratumn/sdk/store/storetestcases"
)

var (
	myCouchstore *CouchStore
	test         *testing.T
	integration  = flag.Bool("integration", false, "Run integration tests")
)

func TestCouchStore(t *testing.T) {
	flag.Parse()
	test = t
	if *integration {
		storetestcases.Factory{
			New:  newTestCouchStore,
			Free: freeTestCouchStore,
		}.RunTests(t)
	}
}

func newTestCouchStore() (store.Adapter, error) {
	config := &Config{
		Address: "http://localhost:5984",
	}
	return New(config)
}

func freeTestCouchStore(a store.Adapter) {
	if err := myCouchstore.deleteDatabase(dbSegment); err != nil {
		test.Fatal(err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"time"
	"strconv"
	"agent/tasks"

	//"github.com/headmade/backuper/agent/tasks"
)

func main() {
	log.Println(time.Now().Format("2006-01-02 15:04:05"))

	types := []string{
		tasks.DIR_TASK_TYPE,
		tasks.POSTGRES_TASK_TYPE,
		"foo",
	}

	for i, t := range types {
		c := tasks.Config{Type: t, Id: strconv.Itoa(i), Params: map[string]string{
			"tmp_path": "/tmp",
			"pass": "123",
			"dir": "/Users/relevv/git/hm/backuper",
			"db_host": "localhost",
			"db_port": "5432",
      "db_user": "dev",
			"db_pass": "123",
			"db_name": "makerton_development",
			"db_tables": "",
		}}
		task, err := tasks.Get(&c)
		if err == nil {
			log.Printf("task type: %s, task object: %#v", t, task)
			btask := task.(tasks.BackupTaskInterface)
			btask.Run()
		} else {
			log.Printf("task type: %s, no registered handler found", t)
		}
	}
}
<commit_msg>Fix task_try<commit_after>package main

import (
	"log"
	"strconv"
	"time"

	"github.com/headmade/backuper/agent/tasks"
)

func main() {
	log.Println(time.Now().Format("2006-01-02 15:04:05"))

	types := []string{
		tasks.DIR_TASK_TYPE,
		tasks.POSTGRES_TASK_TYPE,
		"foo",
	}

	for i, t := range types {
		c := tasks.Config{Type: t, Id: strconv.Itoa(i), Params: map[string]string{
			"tmp_path":  "/tmp",
			"pass":      "123",
			"dir":       "/Users/relevv/git/hm/backuper",
			"db_host":   "localhost",
			"db_port":   "5432",
			"db_user":   "dev",
			"db_pass":   "123",
			"db_name":   "makerton_development",
			"db_tables": "",
		}}
		task, err := tasks.Get(&c)
		if err == nil {
			log.Printf("task type: %s, task object: %#v", t, task)
			btask := task.(tasks.BackupTaskInterface)
			btask.Run()
		} else {
			log.Printf("task type: %s, no registered handler found", t)
		}
	}
}
<|endoftext|>


<commit_before>package tasks

// NOTE we do not log anything in this file! Leave it to the http handler to handle errors.

import (
	"database/sql"
	"time"
)

// TODO add done, done_by, c_time (maybe m_time?)
type Task struct {
	Key        int64     `json:"key"`
	ShortDesc  string    `json:"short_desc"`
	LongDesc   string    `json:"long_desc"`
	Done       bool      `json:"done"`
	DoneBy     string    `json:"done_by"`
	CreateTime time.Time `json:"c_time"`
	ChoreId    int64     `json:"chore_id"`
}

func setChoresNowStmt(db *sql.DB) (*sql.Stmt, error) {
	return db.Prepare("INSERT INTO tasks(chore_id, c_time) SELECT chore_id, NULL from chores where (morning = ? OR night = ?) AND ((dwm = 'd') OR (dwm = 'w' AND day = ?) OR (dwm = 'm' AND date = ?))")
}

func (h *handler) setChoresNow(t time.Time) error {
	var (
		isMorning int
		isNight   int
	)
	if t.Hour() >= 5 || t.Hour() < 17 {
		isMorning = 1
		isNight = -1
	} else {
		isNight = -1
		isMorning = 1
	}
	_, err := h.setChoresNowStmt.Exec(isMorning, isNight, t.Weekday(), t.Day())
	return err
}

func getAllTasksStmt(db *sql.DB) (*sql.Stmt, error) {
	return db.Prepare("SELECT task_id, chores.short_desc, chores.long_desc, done, done_by, tasks.c_time, tasks.chore_id from tasks left join chores on (tasks.chore_id = chores.chore_id)")
}

func (h *handler) getAllTasks() ([]*Task, error) {
	rows, err := h.getAllTasksStmt.Query()
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var (
		taskList   []*Task
		task_id    int64
		short_desc string
		long_desc  string
		done       bool
		done_by    string
		chore_id   int64
		c_time     time.Time
	)
	for rows.Next() {
		err := rows.Scan(&task_id, &short_desc, &long_desc, &done, &done_by, &c_time, &chore_id)
		if err != nil {
			return nil, err
		}
		t := &Task{task_id, short_desc, long_desc, done, done_by, c_time, chore_id}
		taskList = append(taskList, t)
	}
	if err = rows.Err(); err != nil {
		return nil, err
	}
	return taskList, nil
}

func modifyTaskStmt(db *sql.DB) (*sql.Stmt, error) {
	return db.Prepare("UPDATE tasks set done = ?, done_by = ? where task_id = ?")
}

func (h *handler) modifyTask(task *Task) error {
	_, err := h.modifyTaskStmt.Exec(
		task.Done,
		task.DoneBy,
	)
	return err
}
<commit_msg>change nullable field to NullString type<commit_after>package tasks

// NOTE we do not log anything in this file! Leave it to the http handler to handle errors.

import (
	"database/sql"
	"time"
)

// TODO add done, done_by, c_time (maybe m_time?)
type Task struct {
	Key        int64     `json:"key"`
	ShortDesc  string    `json:"short_desc"`
	LongDesc   string    `json:"long_desc"`
	Done       bool      `json:"done"`
	DoneBy     string    `json:"done_by"`
	CreateTime time.Time `json:"c_time"`
	ChoreId    int64     `json:"chore_id"`
}

func setChoresNowStmt(db *sql.DB) (*sql.Stmt, error) {
	return db.Prepare("INSERT INTO tasks(chore_id, c_time) SELECT chore_id, NULL from chores where (morning = ? OR night = ?) AND ((dwm = 'd') OR (dwm = 'w' AND day = ?) OR (dwm = 'm' AND date = ?))")
}

func (h *handler) setChoresNow(t time.Time) error {
	var (
		isMorning int
		isNight   int
	)
	if t.Hour() >= 5 || t.Hour() < 17 {
		isMorning = 1
		isNight = -1
	} else {
		isNight = -1
		isMorning = 1
	}
	_, err := h.setChoresNowStmt.Exec(isMorning, isNight, t.Weekday(), t.Day())
	return err
}

func getAllTasksStmt(db *sql.DB) (*sql.Stmt, error) {
	return db.Prepare("SELECT task_id, chores.short_desc, chores.long_desc, done, done_by, tasks.c_time, tasks.chore_id from tasks left join chores on (tasks.chore_id = chores.chore_id)")
}

func (h *handler) getAllTasks() ([]*Task, error) {
	rows, err := h.getAllTasksStmt.Query()
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var (
		taskList    []*Task
		task_id     int64
		short_desc  string
		long_desc   string
		done        bool
		done_by_raw sql.NullString
		done_by     string
		chore_id    int64
		c_time      time.Time
	)
	for rows.Next() {
		err := rows.Scan(&task_id, &short_desc, &long_desc, &done, &done_by_raw, &c_time, &chore_id)
		if err != nil {
			return nil, err
		}

		if done_by_raw.Valid {
			done_by = done_by_raw.String
		} else {
			done_by = nil
		}

		t := &Task{task_id, short_desc, long_desc, done, done_by, c_time, chore_id}
		taskList = append(taskList, t)
	}
	if err = rows.Err(); err != nil {
		return nil, err
	}
	return taskList, nil
}

func modifyTaskStmt(db *sql.DB) (*sql.Stmt, error) {
	return db.Prepare("UPDATE tasks set done = ?, done_by = ? where task_id = ?")
}

func (h *handler) modifyTask(task *Task) error {
	_, err := h.modifyTaskStmt.Exec(
		task.Done,
		task.DoneBy,
	)
	return err
}
<|endoftext|>


<commit_before>package tau

import (
	"testing"
	"time"

	"hkjn.me/timeutils"
)

type TestTime struct {
	time.Time
	current time.Time
}

func (t TestTime) Since() Tau {
	return Tau(t.current.Sub(t.Time) / 1e9)
}

// newTestTime creates a deterministic TestTime for given value and current time.
func newTestTime(current, value string) TestTime {
	return TestTime{
		Time:    timeutils.Must(timeutils.ParseStd(value)),
		current: timeutils.Must(timeutils.ParseStd(current)),
	}
}

func TestTau(t *testing.T) {
	cases := []struct {
		in   Time
		want Tau
	}{
		{
			in:   newTestTime("2015-06-29 12:00", "2015-06-29 12:00"),
			want: Tau(0),
		},
		{
			in:   newTestTime("2015-06-29 12:00", "2015-06-17 22:14"),
			want: Tau(999960),
		},
		{
			in:   newTestTime("2015-06-29 12:00", "2015-06-17 22:13"),
			want: Tau(1000020),
		},
		{
			in:   newTestTime("2016-11-26 16:47", "1985-03-20 15:00"),
			want: Tau(1000000020),
		},
		{
			in:   newTestTime("2019-03-10 18:34", "1955-10-24 15:00"),
			want: Tau(2000000040),
		},
	}

	for i, tt := range cases {
		out := TauSince(tt.in)
		if tt.want != out {
			t.Errorf("[%d] TauSince(%v) => %v; want %v\n", i, tt.in, out, tt.want)
		}
	}

}

func TestMegaTau(t *testing.T) {
	cases := []struct {
		in   Time
		want MegaTau
	}{
		{
			in:   newTestTime("2015-06-29 12:00", "2015-06-29 12:00"),
			want: MegaTau(0),
		},
		{
			in:   newTestTime("2015-06-29 12:00", "2015-06-17 22:14"),
			want: MegaTau(0),
		},
		{
			in:   newTestTime("2015-06-29 12:00", "2015-06-17 22:13"),
			want: MegaTau(1),
		},
		{
			in:   newTestTime("2016-11-26 16:46", "1985-03-20 15:00"),
			want: MegaTau(999),
		},
		{
			in:   newTestTime("2016-11-26 16:47", "1985-03-20 15:00"),
			want: MegaTau(1000),
		},
		{
			in:   newTestTime("2014-12-03 17:47", "1983-03-27 15:00"),
			want: MegaTau(1000),
		},
		{
			in:   newTestTime("2015-06-26 15:00", "1983-03-27 15:00"),
			want: MegaTau(1017),
		},
		{
			in:   newTestTime("2015-06-26 15:00", "1985-03-20 15:00"),
			want: MegaTau(955),
		},
		{
			// TODO(hkjn): This is the largest time span that can be
			// represented with time.Duration. Add more test cases once this
			// limitation is removed.
			in:   newTestTime("2277-06-25 07:26", "1985-03-20 15:00"),
			want: MegaTau(9222),
		},
	}
	for i, tt := range cases {
		out := TauSince(tt.in).Mega()
		if tt.want != out {
			t.Errorf("[%d] TauSince(%v).Mega() => %v; want %v\n", i, tt.in, out, tt.want)
		}
	}
}

func TestGigaTau(t *testing.T) {
	cases := []struct {
		in   Time
		want GigaTau
	}{
		{
			in:   newTestTime("2015-06-29 12:00", "2015-06-29 12:00"),
			want: GigaTau(0),
		},
		{
			in:   newTestTime("2016-11-26 16:46", "1985-03-20 15:00"),
			want: GigaTau(0),
		},
		{
			in:   newTestTime("2016-11-26 16:47", "1985-03-20 15:00"),
			want: GigaTau(1),
		},
		{
			in:   newTestTime("2019-03-10 18:34", "1955-10-24 15:00"),
			want: GigaTau(2),
		},
		{
			in:   newTestTime("2277-06-25 07:26", "1985-03-20 15:00"),
			want: GigaTau(9),
		},
	}
	for i, tt := range cases {
		out := TauSince(tt.in).Giga()
		if tt.want != out {
			t.Errorf("[%d] TauSince(%v).Giga() => %v; want %v\n", i, tt.in, out, tt.want)
		}
	}
}
<commit_msg>Drop hkjn.me/timeutils dependency<commit_after>package tau

import (
	"log"
	"testing"
	"time"
)

type TestTime struct {
	time.Time
	current time.Time
}

func (t TestTime) Since() Tau {
	return Tau(t.current.Sub(t.Time) / 1e9)
}

// newTestTime creates a deterministic TestTime for given value and current time.
func newTestTime(current, value string) TestTime {
	layout := "2006-01-02 15:04" // a simple time layout
	parse := func(s string) time.Time {
		t, err := time.Parse(layout, s)
		if err != nil {
			log.Fatalf("got err: %v\n", err)
		}
		return t

	}
	return TestTime{
		Time:    parse(value),
		current: parse(current),
	}
}

func TestTau(t *testing.T) {
	cases := []struct {
		in   Time
		want Tau
	}{
		{
			in:   newTestTime("2015-06-29 12:00", "2015-06-29 12:00"),
			want: Tau(0),
		},
		{
			in:   newTestTime("2015-06-29 12:00", "2015-06-17 22:14"),
			want: Tau(999960),
		},
		{
			in:   newTestTime("2015-06-29 12:00", "2015-06-17 22:13"),
			want: Tau(1000020),
		},
		{
			in:   newTestTime("2016-11-26 16:47", "1985-03-20 15:00"),
			want: Tau(1000000020),
		},
		{
			in:   newTestTime("2019-03-10 18:34", "1955-10-24 15:00"),
			want: Tau(2000000040),
		},
	}

	for i, tt := range cases {
		out := TauSince(tt.in)
		if tt.want != out {
			t.Errorf("[%d] TauSince(%v) => %v; want %v\n", i, tt.in, out, tt.want)
		}
	}

}

func TestMegaTau(t *testing.T) {
	cases := []struct {
		in   Time
		want MegaTau
	}{
		{
			in:   newTestTime("2015-06-29 12:00", "2015-06-29 12:00"),
			want: MegaTau(0),
		},
		{
			in:   newTestTime("2015-06-29 12:00", "2015-06-17 22:14"),
			want: MegaTau(0),
		},
		{
			in:   newTestTime("2015-06-29 12:00", "2015-06-17 22:13"),
			want: MegaTau(1),
		},
		{
			in:   newTestTime("2016-11-26 16:46", "1985-03-20 15:00"),
			want: MegaTau(999),
		},
		{
			in:   newTestTime("2016-11-26 16:47", "1985-03-20 15:00"),
			want: MegaTau(1000),
		},
		{
			in:   newTestTime("2014-12-03 17:47", "1983-03-27 15:00"),
			want: MegaTau(1000),
		},
		{
			in:   newTestTime("2015-06-26 15:00", "1983-03-27 15:00"),
			want: MegaTau(1017),
		},
		{
			in:   newTestTime("2015-06-26 15:00", "1985-03-20 15:00"),
			want: MegaTau(955),
		},
		{
			// TODO(hkjn): This is the largest time span that can be
			// represented with time.Duration. Add more test cases once this
			// limitation is removed.
			in:   newTestTime("2277-06-25 07:26", "1985-03-20 15:00"),
			want: MegaTau(9222),
		},
	}
	for i, tt := range cases {
		out := TauSince(tt.in).Mega()
		if tt.want != out {
			t.Errorf("[%d] TauSince(%v).Mega() => %v; want %v\n", i, tt.in, out, tt.want)
		}
	}
}

func TestGigaTau(t *testing.T) {
	cases := []struct {
		in   Time
		want GigaTau
	}{
		{
			in:   newTestTime("2015-06-29 12:00", "2015-06-29 12:00"),
			want: GigaTau(0),
		},
		{
			in:   newTestTime("2016-11-26 16:46", "1985-03-20 15:00"),
			want: GigaTau(0),
		},
		{
			in:   newTestTime("2016-11-26 16:47", "1985-03-20 15:00"),
			want: GigaTau(1),
		},
		{
			in:   newTestTime("2019-03-10 18:34", "1955-10-24 15:00"),
			want: GigaTau(2),
		},
		{
			in:   newTestTime("2277-06-25 07:26", "1985-03-20 15:00"),
			want: GigaTau(9),
		},
	}
	for i, tt := range cases {
		out := TauSince(tt.in).Giga()
		if tt.want != out {
			t.Errorf("[%d] TauSince(%v).Giga() => %v; want %v\n", i, tt.in, out, tt.want)
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2014 Brave New Software

//    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 statshub

import (
	"fmt"
	"github.com/garyburd/redigo/redis"
	"log"
	"strings"
	"time"
)

// StatsUpdate posts stats with zero, one or more dimensions.  Stats
// include Counters (cumulative) and Gauges (point in time)
// (online/offline).
type StatsUpdate struct {
	Dims map[string]string `json:"dims"`
	Stats
	conn redis.Conn
}

// statWriter encapsulates the differences in writing stats between Counters, Increments, Gauges and Members
type statWriter struct {
	// statType: the type of stat handled by this writer (i.e. "counter" or "gauge")
	statType string

	// needsDelta: whether or not the dimensions to be written by this writer need a delta from the written detail
	needsDelta bool

	// writeDetail: writes the detail entry for the given key
	writeDetail func(redisKey string, val interface{}) error

	// expireDetail: expires the detail value (if necessary)
	expireDetail func(redisKey string) error

	// writeDim: writes the dimension entry for the given key
	writeDim func(redisKey string, val interface{}, delta interface{}) error
}

// write posts Counters, Increments, and Gauges and Members for the given id to redis,
// precalculating rollups for each dimension in stats.Dims.
func (stats *StatsUpdate) write(id string) (err error) {
	// Always treat dimensions as lower case
	lowercasedDims := make(map[string]string)
	for name, key := range stats.Dims {
		dimName := strings.ToLower(name)
		dimKey := strings.ToLower(key)
		if dimKey == "total" {
			return fmt.Errorf("Dimension key 'total' is not allowed because it is a reserved word")
		}
		lowercasedDims[dimName] = dimKey
	}
	stats.Dims = lowercasedDims

	if stats.conn, err = connectToRedis(); err != nil {
		return
	}
	defer stats.conn.Close()

	if err = stats.writeCounters(id); err != nil {
		return
	}
	if err = stats.writeIncrements(id); err != nil {
		return
	}
	if err = stats.writeGauges(id); err != nil {
		return
	}
	if err = stats.writeMembers(id); err != nil {
		return
	}

	// Save dims
	for name, value := range stats.Dims {
		err = stats.conn.Send("SADD", "dim", name)
		err = stats.conn.Send("SADD", "dim:"+name, value)
	}
	err = stats.conn.Flush()

	return
}

// writeIncrements increments counters in redis
func (stats *StatsUpdate) writeIncrements(id string) (err error) {
	return stats.doWriteInt(id, stats.Increments, &statWriter{
		statType: "counter",
		writeDetail: func(redisKey string, val interface{}) error {
			// Detail values are simply incremented
			return stats.conn.Send("INCRBY", redisKey, val)
		},
		expireDetail: func(redisKey string) error {
			return nil
		},
		writeDim: func(redisKey string, val interface{}, delta interface{}) error {
			// Rollups are simply incremented
			return stats.conn.Send("INCRBY", redisKey, val)
		},
	})
}

// writeCounters sets counters in redis
func (stats *StatsUpdate) writeCounters(id string) (err error) {
	return stats.doWriteInt(id, stats.Counters, &statWriter{
		statType:   "counter",
		needsDelta: true,
		writeDetail: func(redisKey string, val interface{}) error {
			// Detail values are set using GETSET so that they return their old value
			return stats.conn.Send("GETSET", redisKey, val)
		},
		expireDetail: func(redisKey string) error {
			return nil
		},
		writeDim: func(redisKey string, val interface{}, delta interface{}) error {
			// Rollups are incremented by the delta, which is the new value - old value of the detail
			return stats.conn.Send("INCRBY", redisKey, delta)
		},
	})
}

// writeGauges sets gauges in redis
func (stats *StatsUpdate) writeGauges(id string) (err error) {
	now := time.Now()
	now = now.Truncate(statsPeriod)
	expiration := now.Add(3 * statsPeriod)

	return stats.doWriteInt(id, stats.Gauges, &statWriter{
		statType:   "gauge",
		needsDelta: true,
		writeDetail: func(redisKey string, val interface{}) error {
			// Gauge keys are qualified by the current period's Unix timestamp
			redisKey = keyForPeriod(redisKey, now)
			if strings.Index(redisKey, "fp_ox_at_getlantern_dot_org_c336_1_2014_4_17") >= 0 {
				log.Printf("Recording %s: %d", redisKey, val)
			}
			// Detail values are set using GETSET so that they return their old value
			return stats.conn.Send("GETSET", redisKey, val)
		},
		expireDetail: func(redisKey string) error {
			// Gauge keys are qualified by the current period's Unix timestamp
			redisKey = keyForPeriod(redisKey, now)
			// Detail values are expired every statsPeriod period
			return stats.conn.Send("EXPIREAT", redisKey, expiration.Unix())
		},
		writeDim: func(redisKey string, val interface{}, delta interface{}) error {
			// Gauge keys are qualified by the current period's Unix timestamp
			redisKey = keyForPeriod(redisKey, now)
			if strings.Index(redisKey, "fp_ox_at_getlantern_dot_org_c336_1_2014_4_17") >= 0 {
				log.Printf("Recording %s: %d", redisKey, delta)
			}
			// Rollups are incremented by the delta, which is the new value - old value of the detail
			stats.conn.Send("INCRBY", redisKey, delta)
			// Rollups are expired every statsPeriod period
			return stats.conn.Send("EXPIREAT", redisKey, expiration.Unix())
		},
	})
}

// writeMembers adds members to redis
func (stats *StatsUpdate) writeMembers(id string) (err error) {
	return stats.doWriteString(id, stats.Members, &statWriter{
		statType: "member",
		writeDetail: func(redisKey string, val interface{}) error {
			// Record member in set
			return stats.conn.Send("SADD", redisKey, val)
		},
		expireDetail: func(redisKey string) error {
			return nil
		},
		writeDim: func(redisKey string, val interface{}, oldVal interface{}) error {
			// Record member in set
			return stats.conn.Send("SADD", redisKey, val)
		},
	})
}

func (stats *StatsUpdate) doWriteInt(
	id string,
	values map[string]int64,
	writer *statWriter) (err error) {

	return stats.doWrite(
		id,
		len(values),
		writer,
		func(reportVal func(key string, val interface{}) error) error {
			for key, val := range values {
				reportVal(key, val)
			}
			return nil
		})
}

func (stats *StatsUpdate) doWriteString(
	id string,
	values map[string]string,
	writer *statWriter) (err error) {

	return stats.doWrite(
		id,
		len(values),
		writer,
		func(reportVal func(key string, val interface{}) error) error {
			for key, val := range values {
				reportVal(key, val)
			}
			return nil
		})
}

// doWrite handles the general pattern for writing part of a StatsUpdate (e.g. Counters) to redis
// For each stat key, this means:
//
// 1. Write the detail value
// 2. For each dimension, update the rollup (potentially calculating this based on how the detail value changed relative its prior value)
// 3. Record the stat key so that future queries know which stats to include
func (stats *StatsUpdate) doWrite(
	id string,
	numValues int,
	writer *statWriter,
	iterateValues func(reportVal func(key string, val interface{}) error) error) (err error) {

	// Drain the receive buffer from redis in case previous work hasn't read all its responses
	if err = stats.drainReceiveBuffer(); err != nil {
		return
	}

	keyArgs := make([]interface{}, numValues+1)
	keyArgs[0] = fmt.Sprintf("key:%s", writer.statType)
	i := 1

	// Write detail values
	err = iterateValues(func(key string, val interface{}) error {
		keyArgs[i] = removeDashes(key)
		i++
		detailKey := redisKey(writer.statType, fmt.Sprintf("detail:%s", id), key)
		return writer.writeDetail(detailKey, val)
	})
	if err != nil {
		return
	}

	// Expire detail values
	err = iterateValues(func(key string, val interface{}) error {
		detailKey := redisKey(writer.statType, fmt.Sprintf("detail:%s", id), key)
		return writer.expireDetail(detailKey)
	})
	if err != nil {
		return
	}

	// Flush writes to redis
	if err = stats.conn.Flush(); err != nil {
		return
	}

	iterateValues(func(key string, val interface{}) (err error) {
		var delta int64
		if writer.needsDelta {
			var oldVal int64
			if oldVal, _, err = receive(stats.conn); err != nil {
				return
			}
			delta = val.(int64) - oldVal
		}
		for dimName, dimValue := range stats.Dims {
			dimKey := redisKey(writer.statType, fmt.Sprintf("dim:%s:%s", dimName, dimValue), key)
			err = writer.writeDim(dimKey, val, delta)
		}
		return
	})

	if err != nil {
		return
	}

	// Remember keys
	return stats.conn.Send("SADD", keyArgs...)
}

// drainReceiveBuffer drains any responses in the receive buffer that haven't been read
func (stats *StatsUpdate) drainReceiveBuffer() (err error) {
	_, err = stats.conn.Do("")
	return
}

// withLowerCaseKeys converts the keys in a map to lower case, returning a new
// map with the lower cased keys.
func withLowerCaseKeys(values map[string]uint64) (lowerCased map[string]uint64) {
	lowerCased = make(map[string]uint64)
	for key, value := range values {
		lowerCased[strings.ToLower(key)] = value
	}
	return
}
<commit_msg>getlantern/lantern#1528 Iterating over stat keys in consistent order on write to match up rollups to details<commit_after>// Copyright 2014 Brave New Software

//    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 statshub

import (
	"fmt"
	"github.com/garyburd/redigo/redis"
	"log"
	"sort"
	"strings"
	"time"
)

// StatsUpdate posts stats with zero, one or more dimensions.  Stats
// include Counters (cumulative) and Gauges (point in time)
// (online/offline).
type StatsUpdate struct {
	Dims map[string]string `json:"dims"`
	Stats
	conn redis.Conn
}

// statWriter encapsulates the differences in writing stats between Counters, Increments, Gauges and Members
type statWriter struct {
	// statType: the type of stat handled by this writer (i.e. "counter" or "gauge")
	statType string

	// needsDelta: whether or not the dimensions to be written by this writer need a delta from the written detail
	needsDelta bool

	// writeDetail: writes the detail entry for the given key
	writeDetail func(redisKey string, val interface{}) error

	// expireDetail: expires the detail value (if necessary)
	expireDetail func(redisKey string) error

	// writeDim: writes the dimension entry for the given key
	writeDim func(redisKey string, val interface{}, delta interface{}) error
}

// write posts Counters, Increments, and Gauges and Members for the given id to redis,
// precalculating rollups for each dimension in stats.Dims.
func (stats *StatsUpdate) write(id string) (err error) {
	// Always treat dimensions as lower case
	lowercasedDims := make(map[string]string)
	for name, key := range stats.Dims {
		dimName := strings.ToLower(name)
		dimKey := strings.ToLower(key)
		if dimKey == "total" {
			return fmt.Errorf("Dimension key 'total' is not allowed because it is a reserved word")
		}
		lowercasedDims[dimName] = dimKey
	}
	stats.Dims = lowercasedDims

	if stats.conn, err = connectToRedis(); err != nil {
		return
	}
	defer stats.conn.Close()

	if err = stats.writeCounters(id); err != nil {
		return
	}
	if err = stats.writeIncrements(id); err != nil {
		return
	}
	if err = stats.writeGauges(id); err != nil {
		return
	}
	if err = stats.writeMembers(id); err != nil {
		return
	}

	// Save dims
	for name, value := range stats.Dims {
		err = stats.conn.Send("SADD", "dim", name)
		err = stats.conn.Send("SADD", "dim:"+name, value)
	}
	err = stats.conn.Flush()

	return
}

// writeIncrements increments counters in redis
func (stats *StatsUpdate) writeIncrements(id string) (err error) {
	return stats.doWriteInt(id, stats.Increments, &statWriter{
		statType: "counter",
		writeDetail: func(redisKey string, val interface{}) error {
			// Detail values are simply incremented
			return stats.conn.Send("INCRBY", redisKey, val)
		},
		expireDetail: func(redisKey string) error {
			return nil
		},
		writeDim: func(redisKey string, val interface{}, delta interface{}) error {
			// Rollups are simply incremented
			return stats.conn.Send("INCRBY", redisKey, val)
		},
	})
}

// writeCounters sets counters in redis
func (stats *StatsUpdate) writeCounters(id string) (err error) {
	return stats.doWriteInt(id, stats.Counters, &statWriter{
		statType:   "counter",
		needsDelta: true,
		writeDetail: func(redisKey string, val interface{}) error {
			// Detail values are set using GETSET so that they return their old value
			return stats.conn.Send("GETSET", redisKey, val)
		},
		expireDetail: func(redisKey string) error {
			return nil
		},
		writeDim: func(redisKey string, val interface{}, delta interface{}) error {
			// Rollups are incremented by the delta, which is the new value - old value of the detail
			return stats.conn.Send("INCRBY", redisKey, delta)
		},
	})
}

// writeGauges sets gauges in redis
func (stats *StatsUpdate) writeGauges(id string) (err error) {
	now := time.Now()
	now = now.Truncate(statsPeriod)
	expiration := now.Add(3 * statsPeriod)

	return stats.doWriteInt(id, stats.Gauges, &statWriter{
		statType:   "gauge",
		needsDelta: true,
		writeDetail: func(redisKey string, val interface{}) error {
			// Gauge keys are qualified by the current period's Unix timestamp
			redisKey = keyForPeriod(redisKey, now)
			if strings.Index(redisKey, "fp_ox_at_getlantern_dot_org_c336_1_2014_4_17") >= 0 {
				log.Printf("Recording %s: %d", redisKey, val)
			}
			// Detail values are set using GETSET so that they return their old value
			return stats.conn.Send("GETSET", redisKey, val)
		},
		expireDetail: func(redisKey string) error {
			// Gauge keys are qualified by the current period's Unix timestamp
			redisKey = keyForPeriod(redisKey, now)
			// Detail values are expired every statsPeriod period
			return stats.conn.Send("EXPIREAT", redisKey, expiration.Unix())
		},
		writeDim: func(redisKey string, val interface{}, delta interface{}) error {
			// Gauge keys are qualified by the current period's Unix timestamp
			redisKey = keyForPeriod(redisKey, now)
			if strings.Index(redisKey, "fp_ox_at_getlantern_dot_org_c336_1_2014_4_17") >= 0 {
				log.Printf("Recording %s: %d", redisKey, delta)
			}
			// Rollups are incremented by the delta, which is the new value - old value of the detail
			stats.conn.Send("INCRBY", redisKey, delta)
			// Rollups are expired every statsPeriod period
			return stats.conn.Send("EXPIREAT", redisKey, expiration.Unix())
		},
	})
}

// writeMembers adds members to redis
func (stats *StatsUpdate) writeMembers(id string) (err error) {
	return stats.doWriteString(id, stats.Members, &statWriter{
		statType: "member",
		writeDetail: func(redisKey string, val interface{}) error {
			// Record member in set
			return stats.conn.Send("SADD", redisKey, val)
		},
		expireDetail: func(redisKey string) error {
			return nil
		},
		writeDim: func(redisKey string, val interface{}, oldVal interface{}) error {
			// Record member in set
			return stats.conn.Send("SADD", redisKey, val)
		},
	})
}

func (stats *StatsUpdate) doWriteInt(
	id string,
	values map[string]int64,
	writer *statWriter) (err error) {

	return stats.doWrite(
		id,
		len(values),
		writer,
		func(reportVal func(key string, val interface{}) error) error {
			// Iterate over keys in alphabetical order for consistent ordering
			// between detail updates and corresponding rollups
			keys := make([]string, len(values))
			i := 0
			for key, _ := range values {
				keys[i] = key
				i += 1
			}
			sort.Strings(keys)

			for _, key := range keys {
				val := values[key]
				reportVal(key, val)
			}
			return nil
		})
}

func (stats *StatsUpdate) doWriteString(
	id string,
	values map[string]string,
	writer *statWriter) (err error) {

	return stats.doWrite(
		id,
		len(values),
		writer,
		func(reportVal func(key string, val interface{}) error) error {
			// Iterate over keys in alphabetical order for consistent ordering
			// between detail updates and corresponding rollups
			keys := make([]string, len(values))
			i := 0
			for key, _ := range values {
				keys[i] = key
				i += 1
			}
			sort.Strings(keys)

			for _, key := range keys {
				val := values[key]
				reportVal(key, val)
			}
			return nil
		})
}

// doWrite handles the general pattern for writing part of a StatsUpdate (e.g. Counters) to redis
// For each stat key, this means:
//
// 1. Write the detail value
// 2. For each dimension, update the rollup (potentially calculating this based on how the detail value changed relative its prior value)
// 3. Record the stat key so that future queries know which stats to include
func (stats *StatsUpdate) doWrite(
	id string,
	numValues int,
	writer *statWriter,
	iterateValues func(reportVal func(key string, val interface{}) error) error) (err error) {

	// Drain the receive buffer from redis in case previous work hasn't read all its responses
	if err = stats.drainReceiveBuffer(); err != nil {
		return
	}

	keyArgs := make([]interface{}, numValues+1)
	keyArgs[0] = fmt.Sprintf("key:%s", writer.statType)
	i := 1

	// Write detail values
	err = iterateValues(func(key string, val interface{}) error {
		keyArgs[i] = removeDashes(key)
		i++
		detailKey := redisKey(writer.statType, fmt.Sprintf("detail:%s", id), key)
		return writer.writeDetail(detailKey, val)
	})
	if err != nil {
		return
	}

	// Expire detail values
	err = iterateValues(func(key string, val interface{}) error {
		detailKey := redisKey(writer.statType, fmt.Sprintf("detail:%s", id), key)
		return writer.expireDetail(detailKey)
	})
	if err != nil {
		return
	}

	// Flush writes to redis
	if err = stats.conn.Flush(); err != nil {
		return
	}

	iterateValues(func(key string, val interface{}) (err error) {
		var delta int64
		if writer.needsDelta {
			var oldVal int64
			if oldVal, _, err = receive(stats.conn); err != nil {
				return
			}
			delta = val.(int64) - oldVal
		}
		for dimName, dimValue := range stats.Dims {
			dimKey := redisKey(writer.statType, fmt.Sprintf("dim:%s:%s", dimName, dimValue), key)
			err = writer.writeDim(dimKey, val, delta)
		}
		return
	})

	if err != nil {
		return
	}

	// Remember keys
	return stats.conn.Send("SADD", keyArgs...)
}

// drainReceiveBuffer drains any responses in the receive buffer that haven't been read
func (stats *StatsUpdate) drainReceiveBuffer() (err error) {
	_, err = stats.conn.Do("")
	return
}

// withLowerCaseKeys converts the keys in a map to lower case, returning a new
// map with the lower cased keys.
func withLowerCaseKeys(values map[string]uint64) (lowerCased map[string]uint64) {
	lowerCased = make(map[string]uint64)
	for key, value := range values {
		lowerCased[strings.ToLower(key)] = value
	}
	return
}
<|endoftext|>


<commit_before>package statshub

import (
	"fmt"
	"github.com/garyburd/redigo/redis"
	"strings"
	"time"
)

// StatsUpdate posts stats with zero, one or more dimensions.  Stats
// include Counter (cumulative) and Gauges (point in time)
// (online/offline).
type StatsUpdate struct {
	Dims map[string]string `json:"dims"`
	Stats
}

// postToRedis posts Counter and Gauge for the given id to redis
// using INCRBY and SET respectively.
func (stats *StatsUpdate) postToRedis(id string) (err error) {
	// Always treat dimensions as lower case
	lowercasedDims := make(map[string]string)
	for name, key := range stats.Dims {
		dimName := strings.ToLower(name)
		dimKey := strings.ToLower(key)
		if dimName == "total" {
			return fmt.Errorf("Dimension name 'total' is not allowed because it is a reserved word")
		}
		lowercasedDims[dimName] = dimKey
	}
	stats.Dims = lowercasedDims

	if err = writeIncrement(id, stats); err != nil {
		return
	}
	if err = writeToRedis("counter", id, stats.Dims, stats.Counters); err != nil {
		return
	}
	if err = writeToRedis("gauge", id, stats.Dims, stats.Gauges); err != nil {
		return
	}
	err = writeMembers(id, stats)

	return
}

// writeIncrement increments counters in redis
func writeIncrement(id string, stats *StatsUpdate) (err error) {
	var conn redis.Conn
	if conn, err = connectToRedis(); err != nil {
		return
	}
	defer conn.Close()

	values := stats.Increments
	keyArgs := make([]interface{}, len(values)+1)
	keyArgs[0] = "key:counter"
	i := 1

	for key, value := range values {
		keyArgs[i] = key
		i++
		detailKey := redisKey("counter", fmt.Sprintf("detail:%s", id), key)
		err = conn.Send("INCRBY", detailKey, value)
		for dimName, dimValue := range stats.Dims {
			dimKey := redisKey("counter", fmt.Sprintf("dim:%s:%s", dimName, dimValue), key)
			err = conn.Send("INCRBY", dimKey, value)
		}
	}

	// Remember counter keys
	err = conn.Send("SADD", keyArgs...)

	// Save dims
	for name, value := range stats.Dims {
		err = conn.Send("SADD", "dim", name)
		err = conn.Send("SADD", "dim:"+name, value)
	}

	err = conn.Flush()
	return
}

// writeToRedis writes values (counters or gauges) to redis
func writeToRedis(
	statType string,
	id string,
	dims map[string]string,
	values map[string]int64) (err error) {
	var conn redis.Conn
	if conn, err = connectToRedis(); err != nil {
		return
	}
	defer conn.Close()

	now := time.Now()
	now = now.Truncate(statsPeriod)
	expiration := now.Add(3 * statsPeriod)

	keyArgs := make([]interface{}, len(values)+1)
	keyArgs[0] = "key:" + statType
	i := 1

	isGauge := statType == "gauge"

	qualifiedKey := func(key string) string {
		if isGauge {
			// For gauges, qualify the key with a date
			return fmt.Sprintf("%s:%d", key, now.Unix())
		} else {
			return key
		}
	}

	// Set details
	for key, value := range values {
		keyArgs[i] = key
		i++
		redisKey := redisKey(statType, fmt.Sprintf("detail:%s", id), qualifiedKey(key))
		err = conn.Send("GETSET", redisKey, value)
	}

	if isGauge {
		// The reason we don't do EXPIREAT in the above loop is that the code for
		// dimensional rollups needs to read the return values from GETSET,
		// and we don't want to bother with interleaving those with the EXPIREAT
		// return values.
		for key, _ := range values {
			redisKey := redisKey(statType, fmt.Sprintf("detail:%s", id), qualifiedKey(key))
			err = conn.Send("EXPIREAT", redisKey, expiration.Unix())
		}
	}

	err = conn.Flush()

	// Roll up to dimensions
	for key, value := range values {
		var oldValue int64
		if oldValue, _, err = receive(conn); err != nil {
			return
		}
		delta := value - oldValue
		for dimName, dimValue := range dims {
			dimKey := redisKey(statType, fmt.Sprintf("dim:%s:%s", dimName, dimValue), qualifiedKey(key))
			err = conn.Send("INCRBY", dimKey, delta)
			if isGauge {
				err = conn.Send("EXPIREAT", dimKey, expiration.Unix())
			}
		}
	}

	// Remember keys
	err = conn.Send("SADD", keyArgs...)

	err = conn.Flush()
	return
}

// writeMembers adds members to redis
func writeMembers(id string, stats *StatsUpdate) (err error) {
	var conn redis.Conn
	if conn, err = connectToRedis(); err != nil {
		return
	}
	defer conn.Close()

	values := stats.Members
	keyArgs := make([]interface{}, len(values)+1)
	keyArgs[0] = "key:member"
	i := 1

	for key, value := range values {
		keyArgs[i] = key
		i++
		detailKey := redisKey("member", fmt.Sprintf("detail:%s", id), key)
		err = conn.Send("SADD", detailKey, value)
		for dimName, dimValue := range stats.Dims {
			dimKey := redisKey("member", fmt.Sprintf("dim:%s:%s", dimName, dimValue), key)
			err = conn.Send("SADD", dimKey, value)
		}
	}

	// Remember member keys
	err = conn.Send("SADD", keyArgs...)

	// Save dims //TODO: rather than doing this several times for each type of stat, do it just once
	for name, value := range stats.Dims {
		err = conn.Send("SADD", "dim", name)
		err = conn.Send("SADD", "dim:"+name, value)
	}

	err = conn.Flush()
	return
}

// withLowerCaseKeys converts the keys in a map to lower case, returning a new
// map with the lower cased keys.
func withLowerCaseKeys(values map[string]uint64) (lowerCased map[string]uint64) {
	lowerCased = make(map[string]uint64)
	for key, value := range values {
		lowerCased[strings.ToLower(key)] = value
	}
	return
}
<commit_msg>Added total for all dimensions<commit_after>package statshub

import (
	"fmt"
	"github.com/garyburd/redigo/redis"
	"strings"
	"time"
)

// StatsUpdate posts stats with zero, one or more dimensions.  Stats
// include Counter (cumulative) and Gauges (point in time)
// (online/offline).
type StatsUpdate struct {
	Dims map[string]string `json:"dims"`
	Stats
}

// postToRedis posts Counter and Gauge for the given id to redis
// using INCRBY and SET respectively.
func (stats *StatsUpdate) postToRedis(id string) (err error) {
	// Always treat dimensions as lower case
	lowercasedDims := make(map[string]string)
	for name, key := range stats.Dims {
		dimName := strings.ToLower(name)
		dimKey := strings.ToLower(key)
		if dimKey == "total" {
			return fmt.Errorf("Dimension key 'total' is not allowed because it is a reserved word")
		}
		lowercasedDims[dimName] = dimKey
	}
	stats.Dims = lowercasedDims

	if err = writeIncrement(id, stats); err != nil {
		return
	}
	if err = writeToRedis("counter", id, stats.Dims, stats.Counters); err != nil {
		return
	}
	if err = writeToRedis("gauge", id, stats.Dims, stats.Gauges); err != nil {
		return
	}
	err = writeMembers(id, stats)

	return
}

// writeIncrement increments counters in redis
func writeIncrement(id string, stats *StatsUpdate) (err error) {
	var conn redis.Conn
	if conn, err = connectToRedis(); err != nil {
		return
	}
	defer conn.Close()

	values := stats.Increments
	keyArgs := make([]interface{}, len(values)+1)
	keyArgs[0] = "key:counter"
	i := 1

	for key, value := range values {
		keyArgs[i] = key
		i++
		detailKey := redisKey("counter", fmt.Sprintf("detail:%s", id), key)
		err = conn.Send("INCRBY", detailKey, value)
		for dimName, dimValue := range stats.Dims {
			dimKey := redisKey("counter", fmt.Sprintf("dim:%s:%s", dimName, dimValue), key)
			err = conn.Send("INCRBY", dimKey, value)
		}
	}

	// Remember counter keys
	err = conn.Send("SADD", keyArgs...)

	// Save dims
	for name, value := range stats.Dims {
		err = conn.Send("SADD", "dim", name)
		err = conn.Send("SADD", "dim:"+name, value)
	}

	err = conn.Flush()
	return
}

// writeToRedis writes values (counters or gauges) to redis
func writeToRedis(
	statType string,
	id string,
	dims map[string]string,
	values map[string]int64) (err error) {
	var conn redis.Conn
	if conn, err = connectToRedis(); err != nil {
		return
	}
	defer conn.Close()

	now := time.Now()
	now = now.Truncate(statsPeriod)
	expiration := now.Add(3 * statsPeriod)

	keyArgs := make([]interface{}, len(values)+1)
	keyArgs[0] = "key:" + statType
	i := 1

	isGauge := statType == "gauge"

	qualifiedKey := func(key string) string {
		if isGauge {
			// For gauges, qualify the key with a date
			return fmt.Sprintf("%s:%d", key, now.Unix())
		} else {
			return key
		}
	}

	// Set details
	for key, value := range values {
		keyArgs[i] = key
		i++
		redisKey := redisKey(statType, fmt.Sprintf("detail:%s", id), qualifiedKey(key))
		err = conn.Send("GETSET", redisKey, value)
	}

	if isGauge {
		// The reason we don't do EXPIREAT in the above loop is that the code for
		// dimensional rollups needs to read the return values from GETSET,
		// and we don't want to bother with interleaving those with the EXPIREAT
		// return values.
		for key, _ := range values {
			redisKey := redisKey(statType, fmt.Sprintf("detail:%s", id), qualifiedKey(key))
			err = conn.Send("EXPIREAT", redisKey, expiration.Unix())
		}
	}

	err = conn.Flush()

	// Roll up to dimensions
	for key, value := range values {
		var oldValue int64
		if oldValue, _, err = receive(conn); err != nil {
			return
		}
		delta := value - oldValue
		for dimName, dimValue := range dims {
			dimKey := redisKey(statType, fmt.Sprintf("dim:%s:%s", dimName, dimValue), qualifiedKey(key))
			err = conn.Send("INCRBY", dimKey, delta)
			if isGauge {
				err = conn.Send("EXPIREAT", dimKey, expiration.Unix())
			}
		}
	}

	// Remember keys
	err = conn.Send("SADD", keyArgs...)

	err = conn.Flush()
	return
}

// writeMembers adds members to redis
func writeMembers(id string, stats *StatsUpdate) (err error) {
	var conn redis.Conn
	if conn, err = connectToRedis(); err != nil {
		return
	}
	defer conn.Close()

	values := stats.Members
	keyArgs := make([]interface{}, len(values)+1)
	keyArgs[0] = "key:member"
	i := 1

	for key, value := range values {
		keyArgs[i] = key
		i++
		detailKey := redisKey("member", fmt.Sprintf("detail:%s", id), key)
		err = conn.Send("SADD", detailKey, value)
		for dimName, dimValue := range stats.Dims {
			dimKey := redisKey("member", fmt.Sprintf("dim:%s:%s", dimName, dimValue), key)
			err = conn.Send("SADD", dimKey, value)
		}
	}

	// Remember member keys
	err = conn.Send("SADD", keyArgs...)

	// Save dims //TODO: rather than doing this several times for each type of stat, do it just once
	for name, value := range stats.Dims {
		err = conn.Send("SADD", "dim", name)
		err = conn.Send("SADD", "dim:"+name, value)
	}

	err = conn.Flush()
	return
}

// withLowerCaseKeys converts the keys in a map to lower case, returning a new
// map with the lower cased keys.
func withLowerCaseKeys(values map[string]uint64) (lowerCased map[string]uint64) {
	lowerCased = make(map[string]uint64)
	for key, value := range values {
		lowerCased[strings.ToLower(key)] = value
	}
	return
}
<|endoftext|>


<commit_before>package tools_test

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"sort"
	"strings"
	"sync"
	"testing"

	"github.com/github/git-lfs/tools"
	"github.com/stretchr/testify/assert"
)

func TestCleanPathsCleansPaths(t *testing.T) {
	cleaned := tools.CleanPaths("/foo/bar/,/foo/bar/baz", ",")

	assert.Equal(t, []string{"/foo/bar", "/foo/bar/baz"}, cleaned)
}

func TestCleanPathsReturnsNoResultsWhenGivenNoPaths(t *testing.T) {
	cleaned := tools.CleanPaths("", ",")

	assert.Empty(t, cleaned)
}

type TestIncludeExcludeCase struct {
	expectedResult bool
	includes       []string
	excludes       []string
}

func TestFilterIncludeExclude(t *testing.T) {

	cases := []TestIncludeExcludeCase{
		// Null case
		TestIncludeExcludeCase{true, nil, nil},
		// Inclusion
		TestIncludeExcludeCase{true, []string{"*.dat"}, nil},
		TestIncludeExcludeCase{true, []string{"file*.dat"}, nil},
		TestIncludeExcludeCase{true, []string{"file*"}, nil},
		TestIncludeExcludeCase{true, []string{"*name.dat"}, nil},
		TestIncludeExcludeCase{false, []string{"/*.dat"}, nil},
		TestIncludeExcludeCase{false, []string{"otherfolder/*.dat"}, nil},
		TestIncludeExcludeCase{true, []string{"test/filename.dat"}, nil},
		TestIncludeExcludeCase{true, []string{"test/filename.dat"}, nil},
		TestIncludeExcludeCase{false, []string{"blank", "something", "foo"}, nil},
		TestIncludeExcludeCase{false, []string{"test/notfilename.dat"}, nil},
		TestIncludeExcludeCase{true, []string{"test"}, nil},
		TestIncludeExcludeCase{true, []string{"test/*"}, nil},
		TestIncludeExcludeCase{false, []string{"nottest"}, nil},
		TestIncludeExcludeCase{false, []string{"nottest/*"}, nil},
		TestIncludeExcludeCase{true, []string{"test/fil*"}, nil},
		TestIncludeExcludeCase{false, []string{"test/g*"}, nil},
		TestIncludeExcludeCase{true, []string{"tes*/*"}, nil},
		// Exclusion
		TestIncludeExcludeCase{false, nil, []string{"*.dat"}},
		TestIncludeExcludeCase{false, nil, []string{"file*.dat"}},
		TestIncludeExcludeCase{false, nil, []string{"file*"}},
		TestIncludeExcludeCase{false, nil, []string{"*name.dat"}},
		TestIncludeExcludeCase{true, nil, []string{"/*.dat"}},
		TestIncludeExcludeCase{true, nil, []string{"otherfolder/*.dat"}},
		TestIncludeExcludeCase{false, nil, []string{"test/filename.dat"}},
		TestIncludeExcludeCase{false, nil, []string{"blank", "something", "test/filename.dat", "foo"}},
		TestIncludeExcludeCase{true, nil, []string{"blank", "something", "foo"}},
		TestIncludeExcludeCase{true, nil, []string{"test/notfilename.dat"}},
		TestIncludeExcludeCase{false, nil, []string{"test"}},
		TestIncludeExcludeCase{false, nil, []string{"test/*"}},
		TestIncludeExcludeCase{true, nil, []string{"nottest"}},
		TestIncludeExcludeCase{true, nil, []string{"nottest/*"}},
		TestIncludeExcludeCase{false, nil, []string{"test/fil*"}},
		TestIncludeExcludeCase{true, nil, []string{"test/g*"}},
		TestIncludeExcludeCase{false, nil, []string{"tes*/*"}},

		// Both
		TestIncludeExcludeCase{true, []string{"test/filename.dat"}, []string{"test/notfilename.dat"}},
		TestIncludeExcludeCase{false, []string{"test"}, []string{"test/filename.dat"}},
		TestIncludeExcludeCase{true, []string{"test/*"}, []string{"test/notfile*"}},
		TestIncludeExcludeCase{false, []string{"test/*"}, []string{"test/file*"}},
		TestIncludeExcludeCase{false, []string{"another/*", "test/*"}, []string{"test/notfilename.dat", "test/filename.dat"}},
	}

	for _, c := range cases {
		result := tools.FilenamePassesIncludeExcludeFilter("test/filename.dat", c.includes, c.excludes)
		assert.Equal(t, c.expectedResult, result, "%v", c)
		if runtime.GOOS == "windows" {
			// also test with \ path separators, tolerate mixed separators
			for i, inc := range c.includes {
				c.includes[i] = strings.Replace(inc, "/", "\\", -1)
			}
			for i, ex := range c.excludes {
				c.excludes[i] = strings.Replace(ex, "/", "\\", -1)
			}
			assert.Equal(t, c.expectedResult, tools.FilenamePassesIncludeExcludeFilter("test/filename.dat", c.includes, c.excludes), c)
		}
	}
}

func TestFastWalkBasic(t *testing.T) {
	rootDir, err := ioutil.TempDir(os.TempDir(), "GitLfsTestFastWalkBasic")
	if err != nil {
		assert.FailNow(t, "Unable to get temp dir: %v", err)
	}

	defer os.RemoveAll(rootDir)
	os.Chdir(rootDir)
	dirs := []string{
		"testroot",
		"testroot/folder1",
		"testroot/folder2",
		"testroot/folder2/subfolder1",
		"testroot/folder2/subfolder2",
		"testroot/folder2/subfolder3",
		"testroot/folder2/subfolder4",
		"testroot/folder2/subfolder4/subsub",
	}
	expectedEntries := make([]string, 0, 250)

	for i, dir := range dirs {
		os.MkdirAll(dir, 0755)
		numFiles := 10
		expectedEntries = append(expectedEntries, dir)
		if i >= 3 && i <= 5 {
			// Bulk test to ensure works with > 1 batch
			numFiles = 160
		}
		for f := 0; f < numFiles; f++ {
			filename := filepath.Join(dir, fmt.Sprintf("file%d.txt", f))
			ioutil.WriteFile(filename, []byte("TEST"), 0644)
			expectedEntries = append(expectedEntries, filename)
		}
	}

	fchan, errchan := tools.FastWalk(dirs[0], nil, nil)
	gotEntries, gotErrors := collectFastWalkResults(fchan, errchan)

	assert.Equal(t, 0, len(gotErrors))

	sort.Strings(expectedEntries)
	sort.Strings(gotEntries)
	assert.Equal(t, expectedEntries, gotEntries)

}

func collectFastWalkResults(fchan <-chan tools.FastWalkInfo, errchan <-chan error) ([]string, []error) {
	gotEntries := make([]string, 0, 1000)
	gotErrors := make([]error, 0, 5)
	var waitg sync.WaitGroup
	waitg.Add(2)
	go func() {
		for o := range fchan {
			gotEntries = append(gotEntries, filepath.Join(o.ParentDir, o.Info.Name()))
		}
		waitg.Done()
	}()
	go func() {
		for err := range errchan {
			gotErrors = append(gotErrors, err)
		}
		waitg.Done()
	}()
	waitg.Wait()

	return gotEntries, gotErrors
}
<commit_msg>Add test to eliminate sub-matches from dot<commit_after>package tools_test

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"sort"
	"strings"
	"sync"
	"testing"

	"github.com/github/git-lfs/tools"
	"github.com/stretchr/testify/assert"
)

func TestCleanPathsCleansPaths(t *testing.T) {
	cleaned := tools.CleanPaths("/foo/bar/,/foo/bar/baz", ",")

	assert.Equal(t, []string{"/foo/bar", "/foo/bar/baz"}, cleaned)
}

func TestCleanPathsReturnsNoResultsWhenGivenNoPaths(t *testing.T) {
	cleaned := tools.CleanPaths("", ",")

	assert.Empty(t, cleaned)
}

type TestIncludeExcludeCase struct {
	expectedResult bool
	includes       []string
	excludes       []string
}

func TestFilterIncludeExclude(t *testing.T) {

	cases := []TestIncludeExcludeCase{
		// Null case
		TestIncludeExcludeCase{true, nil, nil},
		// Inclusion
		TestIncludeExcludeCase{true, []string{"*.dat"}, nil},
		TestIncludeExcludeCase{true, []string{"file*.dat"}, nil},
		TestIncludeExcludeCase{true, []string{"file*"}, nil},
		TestIncludeExcludeCase{true, []string{"*name.dat"}, nil},
		TestIncludeExcludeCase{false, []string{"/*.dat"}, nil},
		TestIncludeExcludeCase{false, []string{"otherfolder/*.dat"}, nil},
		TestIncludeExcludeCase{false, []string{"*.nam"}, nil},
		TestIncludeExcludeCase{true, []string{"test/filename.dat"}, nil},
		TestIncludeExcludeCase{true, []string{"test/filename.dat"}, nil},
		TestIncludeExcludeCase{false, []string{"blank", "something", "foo"}, nil},
		TestIncludeExcludeCase{false, []string{"test/notfilename.dat"}, nil},
		TestIncludeExcludeCase{true, []string{"test"}, nil},
		TestIncludeExcludeCase{true, []string{"test/*"}, nil},
		TestIncludeExcludeCase{false, []string{"nottest"}, nil},
		TestIncludeExcludeCase{false, []string{"nottest/*"}, nil},
		TestIncludeExcludeCase{true, []string{"test/fil*"}, nil},
		TestIncludeExcludeCase{false, []string{"test/g*"}, nil},
		TestIncludeExcludeCase{true, []string{"tes*/*"}, nil},
		// Exclusion
		TestIncludeExcludeCase{false, nil, []string{"*.dat"}},
		TestIncludeExcludeCase{false, nil, []string{"file*.dat"}},
		TestIncludeExcludeCase{false, nil, []string{"file*"}},
		TestIncludeExcludeCase{false, nil, []string{"*name.dat"}},
		TestIncludeExcludeCase{true, nil, []string{"/*.dat"}},
		TestIncludeExcludeCase{true, nil, []string{"otherfolder/*.dat"}},
		TestIncludeExcludeCase{false, nil, []string{"test/filename.dat"}},
		TestIncludeExcludeCase{false, nil, []string{"blank", "something", "test/filename.dat", "foo"}},
		TestIncludeExcludeCase{true, nil, []string{"blank", "something", "foo"}},
		TestIncludeExcludeCase{true, nil, []string{"test/notfilename.dat"}},
		TestIncludeExcludeCase{false, nil, []string{"test"}},
		TestIncludeExcludeCase{false, nil, []string{"test/*"}},
		TestIncludeExcludeCase{true, nil, []string{"nottest"}},
		TestIncludeExcludeCase{true, nil, []string{"nottest/*"}},
		TestIncludeExcludeCase{false, nil, []string{"test/fil*"}},
		TestIncludeExcludeCase{true, nil, []string{"test/g*"}},
		TestIncludeExcludeCase{false, nil, []string{"tes*/*"}},

		// Both
		TestIncludeExcludeCase{true, []string{"test/filename.dat"}, []string{"test/notfilename.dat"}},
		TestIncludeExcludeCase{false, []string{"test"}, []string{"test/filename.dat"}},
		TestIncludeExcludeCase{true, []string{"test/*"}, []string{"test/notfile*"}},
		TestIncludeExcludeCase{false, []string{"test/*"}, []string{"test/file*"}},
		TestIncludeExcludeCase{false, []string{"another/*", "test/*"}, []string{"test/notfilename.dat", "test/filename.dat"}},
	}

	for _, c := range cases {
		result := tools.FilenamePassesIncludeExcludeFilter("test/filename.dat", c.includes, c.excludes)
		assert.Equal(t, c.expectedResult, result, "%v", c)
		if runtime.GOOS == "windows" {
			// also test with \ path separators, tolerate mixed separators
			for i, inc := range c.includes {
				c.includes[i] = strings.Replace(inc, "/", "\\", -1)
			}
			for i, ex := range c.excludes {
				c.excludes[i] = strings.Replace(ex, "/", "\\", -1)
			}
			assert.Equal(t, c.expectedResult, tools.FilenamePassesIncludeExcludeFilter("test/filename.dat", c.includes, c.excludes), c)
		}
	}
}

func TestFastWalkBasic(t *testing.T) {
	rootDir, err := ioutil.TempDir(os.TempDir(), "GitLfsTestFastWalkBasic")
	if err != nil {
		assert.FailNow(t, "Unable to get temp dir: %v", err)
	}

	defer os.RemoveAll(rootDir)
	os.Chdir(rootDir)
	dirs := []string{
		"testroot",
		"testroot/folder1",
		"testroot/folder2",
		"testroot/folder2/subfolder1",
		"testroot/folder2/subfolder2",
		"testroot/folder2/subfolder3",
		"testroot/folder2/subfolder4",
		"testroot/folder2/subfolder4/subsub",
	}
	expectedEntries := make([]string, 0, 250)

	for i, dir := range dirs {
		os.MkdirAll(dir, 0755)
		numFiles := 10
		expectedEntries = append(expectedEntries, dir)
		if i >= 3 && i <= 5 {
			// Bulk test to ensure works with > 1 batch
			numFiles = 160
		}
		for f := 0; f < numFiles; f++ {
			filename := filepath.Join(dir, fmt.Sprintf("file%d.txt", f))
			ioutil.WriteFile(filename, []byte("TEST"), 0644)
			expectedEntries = append(expectedEntries, filename)
		}
	}

	fchan, errchan := tools.FastWalk(dirs[0], nil, nil)
	gotEntries, gotErrors := collectFastWalkResults(fchan, errchan)

	assert.Equal(t, 0, len(gotErrors))

	sort.Strings(expectedEntries)
	sort.Strings(gotEntries)
	assert.Equal(t, expectedEntries, gotEntries)

}

func collectFastWalkResults(fchan <-chan tools.FastWalkInfo, errchan <-chan error) ([]string, []error) {
	gotEntries := make([]string, 0, 1000)
	gotErrors := make([]error, 0, 5)
	var waitg sync.WaitGroup
	waitg.Add(2)
	go func() {
		for o := range fchan {
			gotEntries = append(gotEntries, filepath.Join(o.ParentDir, o.Info.Name()))
		}
		waitg.Done()
	}()
	go func() {
		for err := range errchan {
			gotErrors = append(gotErrors, err)
		}
		waitg.Done()
	}()
	waitg.Wait()

	return gotEntries, gotErrors
}
<|endoftext|>


<commit_before>package retry

import (
	"errors"
	"reflect"
	"sync"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

type MyError struct{}

func (e *MyError) Error() string {
	return "Error"
}

type Counter struct {
	sync.Mutex
	count int
}

func (c *Counter) Incr() {
	c.Lock()
	defer c.Unlock()
	c.count++
}

func (c *Counter) Count() int {
	return c.count
}

var _ = Describe("Retrier", func() {
	var backOffOpts *BackOffOpts
	var counter *Counter

	BeforeEach(func() {
		backOffOpts = &BackOffOpts{
			InitialInterval: 1 * time.Nanosecond,
			MaxInterval:     5 * time.Nanosecond,
			MaxElapsedTime:  250 * time.Microsecond}
		counter = &Counter{}
	})

	It("keeps retrying until MaxElapsedTime and calls NotifyGaveUp", func() {
		retrier := NewRetrier("Retrier", backOffOpts, func(error) bool {
			return true
		})

		notifyGaveUpCalled := 0
		retrier.AddNotifyGaveUp(func(*RetryEvent) { notifyGaveUpCalled++ })

		_, err := retrier.Retry(func() (interface{}, error) {
			counter.Incr()
			return 0, &MyError{}
		})

		Expect(err).To(Equal(&MyError{}))

		Expect(counter.Count()).To(BeNumerically(">", 10))
		Expect(notifyGaveUpCalled).To(Equal(1))
	})

	It("retries until successful, calling NotifyRetry on each retry", func() {
		retrier := NewRetrier("Retrier", backOffOpts, func(error) bool {
			return true
		})

		notifyRetryCalled := 0
		retrier.AddNotifyRetry(func(*RetryEvent) { notifyRetryCalled++ })

		iVal, err := retrier.Retry(func() (interface{}, error) {
			counter.Incr()
			if counter.Count() < 5 {
				return 0, &MyError{}
			} else {
				return 123, nil
			}
		})
		Expect(err).NotTo(HaveOccurred())

		val := iVal.(int)
		Expect(counter.Count()).To(Equal(5))
		Expect(val).To(Equal(123))

		// We retried 4 times, for a total of 5 tries.
		Expect(notifyRetryCalled).To(Equal(4))
	})

	It("returns the error when there's a non-retryable error", func() {
		retrier := NewRetrier("Retrier", backOffOpts, func(error) bool {
			return false
		})
		myError := errors.New("myError")
		_, err := retrier.Retry(func() (interface{}, error) {
			return nil, myError
		})
		Expect(err).To(Equal(myError))
	})
})

var _ = Describe("RetryWhenErrorTypeMatches", func() {
	var MyErrorType = reflect.TypeOf(&MyError{})

	It("returns true when an error matches an expected type", func() {
		shouldRetryFunc := RetryWhenErrorTypeMatches([]reflect.Type{MyErrorType})
		Expect(shouldRetryFunc(&MyError{})).To(BeTrue())
		Expect(shouldRetryFunc(errors.New("hi"))).To(BeFalse())
	})
})
<commit_msg>Use testing instead of ginkgo for consistency<commit_after>package retry

import (
	"errors"
	"reflect"
	"sync"
	"testing"
	"time"
)

type MyError struct{}

func (e *MyError) Error() string {
	return "Error"
}

type Counter struct {
	sync.Mutex
	count int
}

func (c *Counter) Incr() {
	c.Lock()
	defer c.Unlock()
	c.count++
}

func (c *Counter) Count() int {
	return c.count
}

var alwaysRetry = func(error) bool { return true }
var neverRetry = func(error) bool { return false }

func createRetrier(shouldRetry func(error) bool) (*Retrier, *Counter) {
	backOffOpts := &BackOffOpts{
		InitialInterval: 1 * time.Nanosecond,
		MaxInterval:     5 * time.Nanosecond,
		MaxElapsedTime:  250 * time.Microsecond}
	counter := &Counter{}

	return NewRetrier("Retrier", backOffOpts, shouldRetry), counter
}

func TestRetriesUntilMaxElapsedTimeAndCallsNotifyGaveUp(t *testing.T) {
	retrier, counter := createRetrier(alwaysRetry)

	notifyGaveUpCalled := 0
	retrier.AddNotifyGaveUp(func(*RetryEvent) { notifyGaveUpCalled++ })

	_, err := retrier.Retry(func() (interface{}, error) {
		counter.Incr()
		return 0, &MyError{}
	})

	want := &MyError{}
	if err != want {
		t.Fatalf("Expected %v, got %v", err, want)
	}
	if counter.Count() <= 10 {
		t.Fatalf("Expected %v > 10", counter.Count())
	}
	if notifyGaveUpCalled != 1 {
		t.Fatalf("Expected notifygaveup to have been called")
	}
}

func TestRetriesUntilSuccessfulCallingNotifyRetryOnEachRetry(t *testing.T) {
	retrier, counter := createRetrier(alwaysRetry)

	notifyRetryCalled := 0
	retrier.AddNotifyRetry(func(*RetryEvent) { notifyRetryCalled++ })

	iVal, err := retrier.Retry(func() (interface{}, error) {
		counter.Incr()
		if counter.Count() < 5 {
			return 0, &MyError{}
		} else {
			return 123, nil
		}
	})

	if err != nil {
		t.Fatalf("Expected no error, got: %v", err)
	}

	val := iVal.(int)

	// We retried 4 times, for a total of 5 tries.
	if counter.Count() != 5 && val != 123 && notifyRetryCalled != 4 {
		t.Fatalf("Unexpected Counter: %v, val %v, notifyRetryCalled: %v", counter.Count(), val, notifyRetryCalled)
	}
}

func TestReturnsTheErrorForNonRetryableErrors(t *testing.T) {
	retrier, _ := createRetrier(neverRetry)
	myError := errors.New("myError")
	_, err := retrier.Retry(func() (interface{}, error) {
		return nil, myError
	})
	if err != myError {
		t.Fatalf("Expected to receive: %v, got %v", myError, err)
	}
}

func TestRetryWhenErrorTypeMatches(t *testing.T) {
	var MyErrorType = reflect.TypeOf(&MyError{})

	shouldRetryFunc := RetryWhenErrorTypeMatches([]reflect.Type{MyErrorType})

	if got := shouldRetryFunc(&MyError{}); got != true {
		t.Fatalf("Expected true")
	}
	if got := shouldRetryFunc(errors.New("hi")); got != false {
		t.Fatalf("Expected false")
	}
}
<|endoftext|>