<commit_before>package main

import (
	"errors"
	"log"
	"net"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/miekg/dns"
)

type Service struct {
	Name    string
	Image   string
	Ip      net.IP
	Ttl     int
	Aliases []string
}

func NewService() (s *Service) {
	s = &Service{Ttl: -1}
	return
}

type ServiceListProvider interface {
	AddService(string, Service)
	RemoveService(string) error
	GetService(string) (Service, error)
	GetAllServices() map[string]Service
}

type DNSServer struct {
	config   *Config
	server   *dns.Server
	mux      *dns.ServeMux
	services map[string]*Service
	lock     *sync.RWMutex
}

func NewDNSServer(c *Config) *DNSServer {
	s := &DNSServer{
		config:   c,
		services: make(map[string]*Service),
		lock:     &sync.RWMutex{},
	}

	s.mux = dns.NewServeMux()
	s.mux.HandleFunc(c.domain.String()+".", s.handleRequest)
	s.mux.HandleFunc("in-addr.arpa.", s.handleReverseRequest)
	s.mux.HandleFunc(".", s.handleForward)

	s.server = &dns.Server{Addr: c.dnsAddr, Net: "udp", Handler: s.mux}

	return s
}

func (s *DNSServer) Start() error {
	return s.server.ListenAndServe()
}

func (s *DNSServer) Stop() {
	s.server.Shutdown()
}

func (s *DNSServer) AddService(id string, service Service) {
	defer s.lock.Unlock()
	s.lock.Lock()

	id = s.getExpandedID(id)
	s.services[id] = &service

	for _, alias := range service.Aliases {
		s.mux.HandleFunc(alias+".", s.handleRequest)
	}

	if s.config.verbose {
		log.Println("Added service:", id, service)
	}
}

func (s *DNSServer) RemoveService(id string) error {
	defer s.lock.Unlock()
	s.lock.Lock()

	id = s.getExpandedID(id)
	if _, ok := s.services[id]; !ok {
		return errors.New("No such service: " + id)
	}

	for _, alias := range s.services[id].Aliases {
		s.mux.HandleRemove(alias + ".")
	}

	delete(s.services, id)

	if s.config.verbose {
		log.Println("Stopped service:", id)
	}

	return nil
}

func (s *DNSServer) GetService(id string) (Service, error) {
	defer s.lock.RUnlock()
	s.lock.RLock()

	id = s.getExpandedID(id)
	if s, ok := s.services[id]; ok {
		return *s, nil
	}
	// Check for a pa
	return *new(Service), errors.New("No such service: " + id)
}

func (s *DNSServer) GetAllServices() map[string]Service {
	defer s.lock.RUnlock()
	s.lock.RLock()

	list := make(map[string]Service, len(s.services))
	for id, service := range s.services {
		list[id] = *service
	}

	return list
}

func (s *DNSServer) listDomains(service *Service) chan string {
	c := make(chan string)

	go func() {

		if service.Image == "" {
			c <- service.Name + "." + s.config.domain.String() + "."
		} else {
			domain := service.Image + "." + s.config.domain.String() + "."

			c <- domain
			c <- service.Name + "." + domain
		}

		for _, alias := range service.Aliases {
			c <- alias + "."
		}

		close(c)
	}()

	return c
}

func (s *DNSServer) handleForward(w dns.ResponseWriter, r *dns.Msg) {
	if s.config.verbose {
		log.Println("Using DNS forwarding")
	}
	// Otherwise just forward the request to another server
	c := new(dns.Client)

	// look at each nameserver, stop on success
	for i := range s.config.nameserver {
		if s.config.verbose {
			log.Println("Using nameserver " + s.config.nameserver[i])
		}

		in, _, err := c.Exchange(r, s.config.nameserver[i])
		if err == nil {
			w.WriteMsg(in)
			return
		} else {
			if i == (len(s.config.nameserver) - 1) {
				log.Println("Error forwarding DNS: " + err.Error() + ": fatal, no more nameservers to try")
			} else {
				log.Println("Error forwarding DNS: " + err.Error() + ": trying next nameserver")
			}

			m := new(dns.Msg)
			m.SetReply(r)
			m.Ns = s.createSOA()
			m.SetRcode(r, dns.RcodeRefused) // REFUSED

			w.WriteMsg(m)
		}
	}
}

func (s *DNSServer) makeServiceA(n string, service *Service) dns.RR {
	rr := new(dns.A)

	var ttl int
	if service.Ttl != -1 {
		ttl = service.Ttl
	} else {
		ttl = s.config.ttl
	}

	rr.Hdr = dns.RR_Header{
		Name:   n,
		Rrtype: dns.TypeA,
		Class:  dns.ClassINET,
		Ttl:    uint32(ttl),
	}

	rr.A = service.Ip

	return rr
}

func (s *DNSServer) makeServiceMX(n string, service *Service) dns.RR {
	rr := new(dns.MX)

	var ttl int
	if service.Ttl != -1 {
		ttl = service.Ttl
	} else {
		ttl = s.config.ttl
	}

	rr.Hdr = dns.RR_Header{
		Name:   n,
		Rrtype: dns.TypeMX,
		Class:  dns.ClassINET,
		Ttl:    uint32(ttl),
	}

	rr.Mx = n

	return rr
}

func (s *DNSServer) handleRequest(w dns.ResponseWriter, r *dns.Msg) {
	m := new(dns.Msg)
	m.SetReply(r)

	// Send empty response for empty requests
	if len(r.Question) == 0 {
		m.Ns = s.createSOA()
		w.WriteMsg(m)
		return
	}

	// respond to SOA requests
	if r.Question[0].Qtype == dns.TypeSOA {
		m.Answer = s.createSOA()
		w.WriteMsg(m)
		return
	}

	m.Answer = make([]dns.RR, 0, 2)
	query := r.Question[0].Name

	// trim off any trailing dot
	if query[len(query)-1] == '.' {
		query = query[:len(query)-1]
	}

	for service := range s.queryServices(query) {
		var rr dns.RR
		switch r.Question[0].Qtype {
		case dns.TypeA:
			rr = s.makeServiceA(r.Question[0].Name, service)
		case dns.TypeMX:
			rr = s.makeServiceMX(r.Question[0].Name, service)
		default:
			// this query type isn't supported, but we do have
			// a record with this name. Per RFC 4074 sec. 3, we
			// immediately return an empty NOERROR reply.
			m.Ns = s.createSOA()
			w.WriteMsg(m)
			return
		}

		m.Answer = append(m.Answer, rr)
	}

	// We didn't find a record corresponding to the query
	if len(m.Answer) == 0 {
		m.Ns = s.createSOA()
		m.SetRcode(r, dns.RcodeNameError) // NXDOMAIN
	}

	w.WriteMsg(m)
}

func (s *DNSServer) handleReverseRequest(w dns.ResponseWriter, r *dns.Msg) {
	m := new(dns.Msg)
	m.SetReply(r)

	// Send empty response for empty requests
	if len(r.Question) == 0 {
		m.Ns = s.createSOA()
		w.WriteMsg(m)
		return
	}

	m.Answer = make([]dns.RR, 0, 2)
	query := r.Question[0].Name

	// trim off any trailing dot
	if query[len(query)-1] == '.' {
		query = query[:len(query)-1]
	}

	for service := range s.queryIp(query) {
		if r.Question[0].Qtype != dns.TypePTR {
			m.Ns = s.createSOA()
			w.WriteMsg(m)
			return
		}

		var ttl int
		if service.Ttl != -1 {
			ttl = service.Ttl
		} else {
			ttl = s.config.ttl
		}

		for domain := range s.listDomains(service) {
			rr := new(dns.PTR)
			rr.Hdr = dns.RR_Header{
				Name:   r.Question[0].Name,
				Rrtype: dns.TypePTR,
				Class:  dns.ClassINET,
				Ttl:    uint32(ttl),
			}
			rr.Ptr = domain

			m.Answer = append(m.Answer, rr)
		}
	}

	if len(m.Answer) != 0 {
		w.WriteMsg(m)
	} else {
		// We didn't find a record corresponding to the query,
		// try forwarding
		s.handleForward(w, r)
	}
}

func (s *DNSServer) queryIp(query string) chan *Service {
	c := make(chan *Service, 3)
	reversedIp := strings.TrimSuffix(query, ".in-addr.arpa")
	ip := strings.Join(reverse(strings.Split(reversedIp, ".")), ".")

	go func() {
		defer s.lock.RUnlock()
		s.lock.RLock()

		for _, service := range s.services {
			if service.Ip.String() == ip {
				c <- service
			}
		}

		close(c)
	}()

	return c
}

func (s *DNSServer) queryServices(query string) chan *Service {
	c := make(chan *Service, 3)

	go func() {
		query := strings.Split(strings.ToLower(query), ".")

		defer s.lock.RUnlock()
		s.lock.RLock()

		for _, service := range s.services {
			// create the name for this service, skip empty strings
			test := []string{}
			// todo: add some cache to avoid calculating this every time
			if len(service.Name) > 0 {
				test = append(test, strings.Split(service.Name, ".")...)
			}

			if len(service.Image) > 0 {
				test = append(test, strings.Split(service.Image, ".")...)
			}

			test = append(test, s.config.domain...)

			if isPrefixQuery(query, test) {
				c <- service
			}

			// check aliases
			for _, alias := range service.Aliases {
				if isPrefixQuery(query, strings.Split(alias, ".")) {
					c <- service
				}
			}
		}

		close(c)

	}()

	return c

}

// Checks for a partial match for container SHA and outputs it if found.
func (s *DNSServer) getExpandedID(in string) (out string) {
	out = in

	// Hard to make a judgement on small image names.
	if len(in) < 4 {
		return
	}

	if isHex, _ := regexp.MatchString("^[0-9a-f]+$", in); !isHex {
		return
	}

	for id := range s.services {
		if len(id) == 64 {
			if isHex, _ := regexp.MatchString("^[0-9a-f]+$", id); isHex {
				if strings.HasPrefix(id, in) {
					out = id
					return
				}
			}
		}
	}
	return
}

// Ttl is used from config so that not-found result responses are not cached
// for a long time. The other defaults left as is(skydns source) because they
// do not have an use case in this situation.
func (s *DNSServer) createSOA() []dns.RR {
	dom := dns.Fqdn(s.config.domain.String() + ".")
	soa := &dns.SOA{
		Hdr: dns.RR_Header{
			Name:   dom,
			Rrtype: dns.TypeSOA,
			Class:  dns.ClassINET,
			Ttl:    uint32(s.config.ttl)},
		Ns:      "dnsdock." + dom,
		Mbox:    "dnsdock.dnsdock." + dom,
		Serial:  uint32(time.Now().Truncate(time.Hour).Unix()),
		Refresh: 28800,
		Retry:   7200,
		Expire:  604800,
		Minttl:  uint32(s.config.ttl),
	}
	return []dns.RR{soa}
}

// isPrefixQuery is used to determine whether "query" is a potential prefix
// query for "name". It allows for wildcards (*) in the query. However is makes
// one exception to accomodate the desired behavior we wish from dnsdock,
// namely, the query may be longer than "name" and still be a valid prefix
// query for "name".
// Examples:
//   foo.bar.baz.qux is a valid query for bar.baz.qux (longer prefix is okay)
//   foo.*.baz.qux   is a valid query for bar.baz.qux (wildcards okay)
//   *.baz.qux       is a valid query for baz.baz.qux (wildcard prefix okay)
func isPrefixQuery(query, name []string) bool {
	for i, j := len(query)-1, len(name)-1; i >= 0 && j >= 0; i, j = i-1, j-1 {
		if query[i] != name[j] && query[i] != "*" {
			return false
		}
	}
	return true
}

func reverse(input []string) []string {
	if len(input) == 0 {
		return input
	}

	return append(reverse(input[1:]), input[0])
}
<commit_msg>include the query in the forward verbose msg<commit_after>package main

import (
	"errors"
	"log"
	"net"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/miekg/dns"
)

type Service struct {
	Name    string
	Image   string
	Ip      net.IP
	Ttl     int
	Aliases []string
}

func NewService() (s *Service) {
	s = &Service{Ttl: -1}
	return
}

type ServiceListProvider interface {
	AddService(string, Service)
	RemoveService(string) error
	GetService(string) (Service, error)
	GetAllServices() map[string]Service
}

type DNSServer struct {
	config   *Config
	server   *dns.Server
	mux      *dns.ServeMux
	services map[string]*Service
	lock     *sync.RWMutex
}

func NewDNSServer(c *Config) *DNSServer {
	s := &DNSServer{
		config:   c,
		services: make(map[string]*Service),
		lock:     &sync.RWMutex{},
	}

	s.mux = dns.NewServeMux()
	s.mux.HandleFunc(c.domain.String()+".", s.handleRequest)
	s.mux.HandleFunc("in-addr.arpa.", s.handleReverseRequest)
	s.mux.HandleFunc(".", s.handleForward)

	s.server = &dns.Server{Addr: c.dnsAddr, Net: "udp", Handler: s.mux}

	return s
}

func (s *DNSServer) Start() error {
	return s.server.ListenAndServe()
}

func (s *DNSServer) Stop() {
	s.server.Shutdown()
}

func (s *DNSServer) AddService(id string, service Service) {
	defer s.lock.Unlock()
	s.lock.Lock()

	id = s.getExpandedID(id)
	s.services[id] = &service

	for _, alias := range service.Aliases {
		s.mux.HandleFunc(alias+".", s.handleRequest)
	}

	if s.config.verbose {
		log.Println("Added service:", id, service)
	}
}

func (s *DNSServer) RemoveService(id string) error {
	defer s.lock.Unlock()
	s.lock.Lock()

	id = s.getExpandedID(id)
	if _, ok := s.services[id]; !ok {
		return errors.New("No such service: " + id)
	}

	for _, alias := range s.services[id].Aliases {
		s.mux.HandleRemove(alias + ".")
	}

	delete(s.services, id)

	if s.config.verbose {
		log.Println("Stopped service:", id)
	}

	return nil
}

func (s *DNSServer) GetService(id string) (Service, error) {
	defer s.lock.RUnlock()
	s.lock.RLock()

	id = s.getExpandedID(id)
	if s, ok := s.services[id]; ok {
		return *s, nil
	}
	// Check for a pa
	return *new(Service), errors.New("No such service: " + id)
}

func (s *DNSServer) GetAllServices() map[string]Service {
	defer s.lock.RUnlock()
	s.lock.RLock()

	list := make(map[string]Service, len(s.services))
	for id, service := range s.services {
		list[id] = *service
	}

	return list
}

func (s *DNSServer) listDomains(service *Service) chan string {
	c := make(chan string)

	go func() {

		if service.Image == "" {
			c <- service.Name + "." + s.config.domain.String() + "."
		} else {
			domain := service.Image + "." + s.config.domain.String() + "."

			c <- domain
			c <- service.Name + "." + domain
		}

		for _, alias := range service.Aliases {
			c <- alias + "."
		}

		close(c)
	}()

	return c
}

func (s *DNSServer) handleForward(w dns.ResponseWriter, r *dns.Msg) {
	if s.config.verbose {
		log.Println("Using DNS forwarding for " + r.Question[0].Name)
	}
	// Otherwise just forward the request to another server
	c := new(dns.Client)

	// look at each nameserver, stop on success
	for i := range s.config.nameserver {
		if s.config.verbose {
			log.Println("Using nameserver " + s.config.nameserver[i])
		}

		in, _, err := c.Exchange(r, s.config.nameserver[i])
		if err == nil {
			w.WriteMsg(in)
			return
		} else {
			if i == (len(s.config.nameserver) - 1) {
				log.Println("Error forwarding DNS: " + err.Error() + ": fatal, no more nameservers to try")
			} else {
				log.Println("Error forwarding DNS: " + err.Error() + ": trying next nameserver")
			}

			m := new(dns.Msg)
			m.SetReply(r)
			m.Ns = s.createSOA()
			m.SetRcode(r, dns.RcodeRefused) // REFUSED

			w.WriteMsg(m)
		}
	}
}

func (s *DNSServer) makeServiceA(n string, service *Service) dns.RR {
	rr := new(dns.A)

	var ttl int
	if service.Ttl != -1 {
		ttl = service.Ttl
	} else {
		ttl = s.config.ttl
	}

	rr.Hdr = dns.RR_Header{
		Name:   n,
		Rrtype: dns.TypeA,
		Class:  dns.ClassINET,
		Ttl:    uint32(ttl),
	}

	rr.A = service.Ip

	return rr
}

func (s *DNSServer) makeServiceMX(n string, service *Service) dns.RR {
	rr := new(dns.MX)

	var ttl int
	if service.Ttl != -1 {
		ttl = service.Ttl
	} else {
		ttl = s.config.ttl
	}

	rr.Hdr = dns.RR_Header{
		Name:   n,
		Rrtype: dns.TypeMX,
		Class:  dns.ClassINET,
		Ttl:    uint32(ttl),
	}

	rr.Mx = n

	return rr
}

func (s *DNSServer) handleRequest(w dns.ResponseWriter, r *dns.Msg) {
	m := new(dns.Msg)
	m.SetReply(r)

	// Send empty response for empty requests
	if len(r.Question) == 0 {
		m.Ns = s.createSOA()
		w.WriteMsg(m)
		return
	}

	// respond to SOA requests
	if r.Question[0].Qtype == dns.TypeSOA {
		m.Answer = s.createSOA()
		w.WriteMsg(m)
		return
	}

	m.Answer = make([]dns.RR, 0, 2)
	query := r.Question[0].Name

	// trim off any trailing dot
	if query[len(query)-1] == '.' {
		query = query[:len(query)-1]
	}

	for service := range s.queryServices(query) {
		var rr dns.RR
		switch r.Question[0].Qtype {
		case dns.TypeA:
			rr = s.makeServiceA(r.Question[0].Name, service)
		case dns.TypeMX:
			rr = s.makeServiceMX(r.Question[0].Name, service)
		default:
			// this query type isn't supported, but we do have
			// a record with this name. Per RFC 4074 sec. 3, we
			// immediately return an empty NOERROR reply.
			m.Ns = s.createSOA()
			w.WriteMsg(m)
			return
		}

		m.Answer = append(m.Answer, rr)
	}

	// We didn't find a record corresponding to the query
	if len(m.Answer) == 0 {
		m.Ns = s.createSOA()
		m.SetRcode(r, dns.RcodeNameError) // NXDOMAIN
	}

	w.WriteMsg(m)
}

func (s *DNSServer) handleReverseRequest(w dns.ResponseWriter, r *dns.Msg) {
	m := new(dns.Msg)
	m.SetReply(r)

	// Send empty response for empty requests
	if len(r.Question) == 0 {
		m.Ns = s.createSOA()
		w.WriteMsg(m)
		return
	}

	m.Answer = make([]dns.RR, 0, 2)
	query := r.Question[0].Name

	// trim off any trailing dot
	if query[len(query)-1] == '.' {
		query = query[:len(query)-1]
	}

	for service := range s.queryIp(query) {
		if r.Question[0].Qtype != dns.TypePTR {
			m.Ns = s.createSOA()
			w.WriteMsg(m)
			return
		}

		var ttl int
		if service.Ttl != -1 {
			ttl = service.Ttl
		} else {
			ttl = s.config.ttl
		}

		for domain := range s.listDomains(service) {
			rr := new(dns.PTR)
			rr.Hdr = dns.RR_Header{
				Name:   r.Question[0].Name,
				Rrtype: dns.TypePTR,
				Class:  dns.ClassINET,
				Ttl:    uint32(ttl),
			}
			rr.Ptr = domain

			m.Answer = append(m.Answer, rr)
		}
	}

	if len(m.Answer) != 0 {
		w.WriteMsg(m)
	} else {
		// We didn't find a record corresponding to the query,
		// try forwarding
		s.handleForward(w, r)
	}
}

func (s *DNSServer) queryIp(query string) chan *Service {
	c := make(chan *Service, 3)
	reversedIp := strings.TrimSuffix(query, ".in-addr.arpa")
	ip := strings.Join(reverse(strings.Split(reversedIp, ".")), ".")

	go func() {
		defer s.lock.RUnlock()
		s.lock.RLock()

		for _, service := range s.services {
			if service.Ip.String() == ip {
				c <- service
			}
		}

		close(c)
	}()

	return c
}

func (s *DNSServer) queryServices(query string) chan *Service {
	c := make(chan *Service, 3)

	go func() {
		query := strings.Split(strings.ToLower(query), ".")

		defer s.lock.RUnlock()
		s.lock.RLock()

		for _, service := range s.services {
			// create the name for this service, skip empty strings
			test := []string{}
			// todo: add some cache to avoid calculating this every time
			if len(service.Name) > 0 {
				test = append(test, strings.Split(service.Name, ".")...)
			}

			if len(service.Image) > 0 {
				test = append(test, strings.Split(service.Image, ".")...)
			}

			test = append(test, s.config.domain...)

			if isPrefixQuery(query, test) {
				c <- service
			}

			// check aliases
			for _, alias := range service.Aliases {
				if isPrefixQuery(query, strings.Split(alias, ".")) {
					c <- service
				}
			}
		}

		close(c)

	}()

	return c

}

// Checks for a partial match for container SHA and outputs it if found.
func (s *DNSServer) getExpandedID(in string) (out string) {
	out = in

	// Hard to make a judgement on small image names.
	if len(in) < 4 {
		return
	}

	if isHex, _ := regexp.MatchString("^[0-9a-f]+$", in); !isHex {
		return
	}

	for id := range s.services {
		if len(id) == 64 {
			if isHex, _ := regexp.MatchString("^[0-9a-f]+$", id); isHex {
				if strings.HasPrefix(id, in) {
					out = id
					return
				}
			}
		}
	}
	return
}

// Ttl is used from config so that not-found result responses are not cached
// for a long time. The other defaults left as is(skydns source) because they
// do not have an use case in this situation.
func (s *DNSServer) createSOA() []dns.RR {
	dom := dns.Fqdn(s.config.domain.String() + ".")
	soa := &dns.SOA{
		Hdr: dns.RR_Header{
			Name:   dom,
			Rrtype: dns.TypeSOA,
			Class:  dns.ClassINET,
			Ttl:    uint32(s.config.ttl)},
		Ns:      "dnsdock." + dom,
		Mbox:    "dnsdock.dnsdock." + dom,
		Serial:  uint32(time.Now().Truncate(time.Hour).Unix()),
		Refresh: 28800,
		Retry:   7200,
		Expire:  604800,
		Minttl:  uint32(s.config.ttl),
	}
	return []dns.RR{soa}
}

// isPrefixQuery is used to determine whether "query" is a potential prefix
// query for "name". It allows for wildcards (*) in the query. However is makes
// one exception to accomodate the desired behavior we wish from dnsdock,
// namely, the query may be longer than "name" and still be a valid prefix
// query for "name".
// Examples:
//   foo.bar.baz.qux is a valid query for bar.baz.qux (longer prefix is okay)
//   foo.*.baz.qux   is a valid query for bar.baz.qux (wildcards okay)
//   *.baz.qux       is a valid query for baz.baz.qux (wildcard prefix okay)
func isPrefixQuery(query, name []string) bool {
	for i, j := len(query)-1, len(name)-1; i >= 0 && j >= 0; i, j = i-1, j-1 {
		if query[i] != name[j] && query[i] != "*" {
			return false
		}
	}
	return true
}

func reverse(input []string) []string {
	if len(input) == 0 {
		return input
	}

	return append(reverse(input[1:]), input[0])
}
<|endoftext|>


<commit_before>// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cgotest

import (
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"testing"
)

// TestCrossPackageTests compiles and runs tests that depend on imports of other
// local packages, using source code stored in the testdata directory.
//
// The tests in the misc directory tree do not have a valid import path in
// GOPATH mode, so they previously used relative imports. However, relative
// imports do not work in module mode. In order to make the test work in both
// modes, we synthesize a GOPATH in which the module paths are equivalent, and
// run the tests as a subprocess.
//
// If and when we no longer support these tests in GOPATH mode, we can remove
// this shim and move the tests currently located in testdata back into the
// parent directory.
func TestCrossPackageTests(t *testing.T) {
	if runtime.GOOS == "darwin" {
		switch runtime.GOARCH {
		case "arm", "arm64":
			t.Skip("Can't exec cmd/go subprocess on iOS.")
		}
	}

	GOPATH, err := ioutil.TempDir("", "cgotest")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(GOPATH)

	modRoot := filepath.Join(GOPATH, "src", "cgotest")
	if err := overlayDir(modRoot, "testdata"); err != nil {
		t.Fatal(err)
	}
	if err := ioutil.WriteFile(filepath.Join(modRoot, "go.mod"), []byte("module cgotest\n"), 0666); err != nil {
		t.Fatal(err)
	}

	cmd := exec.Command("go", "test")
	if testing.Verbose() {
		cmd.Args = append(cmd.Args, "-v")
	}
	if testing.Short() {
		cmd.Args = append(cmd.Args, "-short")
	}
	cmd.Dir = modRoot
	cmd.Env = append(os.Environ(), "GOPATH="+GOPATH)
	out, err := cmd.CombinedOutput()
	if err == nil {
		t.Logf("%s:\n%s", strings.Join(cmd.Args, " "), out)
	} else {
		t.Fatalf("%s: %s\n%s", strings.Join(cmd.Args, " "), err, out)
	}
}
<commit_msg>misc/cgo/test: skip TestCrossPackageTests on Android<commit_after>// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cgotest

import (
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"testing"
)

// TestCrossPackageTests compiles and runs tests that depend on imports of other
// local packages, using source code stored in the testdata directory.
//
// The tests in the misc directory tree do not have a valid import path in
// GOPATH mode, so they previously used relative imports. However, relative
// imports do not work in module mode. In order to make the test work in both
// modes, we synthesize a GOPATH in which the module paths are equivalent, and
// run the tests as a subprocess.
//
// If and when we no longer support these tests in GOPATH mode, we can remove
// this shim and move the tests currently located in testdata back into the
// parent directory.
func TestCrossPackageTests(t *testing.T) {
	switch runtime.GOOS {
	case "android":
		t.Skip("Can't exec cmd/go subprocess on Android.")
	case "darwin":
		switch runtime.GOARCH {
		case "arm", "arm64":
			t.Skip("Can't exec cmd/go subprocess on iOS.")
		}
	}

	GOPATH, err := ioutil.TempDir("", "cgotest")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(GOPATH)

	modRoot := filepath.Join(GOPATH, "src", "cgotest")
	if err := overlayDir(modRoot, "testdata"); err != nil {
		t.Fatal(err)
	}
	if err := ioutil.WriteFile(filepath.Join(modRoot, "go.mod"), []byte("module cgotest\n"), 0666); err != nil {
		t.Fatal(err)
	}

	cmd := exec.Command("go", "test")
	if testing.Verbose() {
		cmd.Args = append(cmd.Args, "-v")
	}
	if testing.Short() {
		cmd.Args = append(cmd.Args, "-short")
	}
	cmd.Dir = modRoot
	cmd.Env = append(os.Environ(), "GOPATH="+GOPATH)
	out, err := cmd.CombinedOutput()
	if err == nil {
		t.Logf("%s:\n%s", strings.Join(cmd.Args, " "), out)
	} else {
		t.Fatalf("%s: %s\n%s", strings.Join(cmd.Args, " "), err, out)
	}
}
<|endoftext|>


<commit_before>package mixpanel

import (
	"fmt"
	"strings"
	"testing"
	"time"
)

func TestAddSignature(t *testing.T) {
	// TODO: write me
}

func TestMakeArgs(t *testing.T) {
	mix := New("product", "key", "secret")
	date, _ := time.Parse("2006-01-02", "1999-12-31")
	args := mix.makeArgs(date)

	expected := [][]string{
		{"format", "json"},
		{"api_key", "key"},
		{"from_date", "1999-12-31"},
		{"to_date", "1999-12-31"},
	}

	for _, pair := range expected {
		if args[pair[0]][0] != pair[1] {
			t.Errorf("Expected %v, got %s", pair, args[pair[0]][0])
		}
	}
}

func TestTransformEventData(t *testing.T) {
	mix := New("product", "", "")
	input := strings.NewReader(`
{"event": "a0", "properties": {"a": null, "b": "b0", "c": true, "d": ["foo"]}}
{"event": "a1", "properties": {"a": null, "b": "b1", "c": true, "d": ["foo"]}}
{"event": "a2", "properties": {"a": null, "b": "b2", "c": true, "d": ["foo"]}}`)

	output := make(chan EventData)

	go func() {
		if num, err := mix.TransformEventData(input, output); err != nil {
			t.Errorf("raised error: %v", err)
		} else if num != 3 {
			t.Errorf("expected to see 3 records, saw %d", num)
		}
	}()

	for i := 0; i < 3; i++ {
		event := <-output

		expected := []struct {
			Name  string
			Value interface{}
		}{
			{"event", fmt.Sprintf("a%d", i)},
			{"a", nil},
			{"b", fmt.Sprintf("b%d", i)},
			{"c", true},
		}

		for _, e := range expected {
			if v, ok := event[e.Name]; !ok || v != e.Value {
				t.Errorf("bad value: expected %s=(%v) got %s=(%v)", e.Name, e.Value,
					e.Name, v)
			}
		}
	}
	close(output)
}

func TestTransformEventDataApiError(t *testing.T) {
	mix := New("product", "", "")
	input := strings.NewReader(`{"error": "some api error"}`)

	output := make(chan EventData)

	if num, err := mix.TransformEventData(input, output); err == nil {
		t.Error("Expected error on bad json")
	} else if err.Error() != "product: API error: some api error" {
		t.Errorf("Bad error string: '%s'", err.Error())
	} else if num != 0 {
		t.Errorf("Expected 0 records, got %d", num)
	}
}

func TestTransformEventDataBadJson(t *testing.T) {
	mix := New("product", "", "")
	input := strings.NewReader(`{"event": "a", "properties": {"a": "1"}}
{"event": "bad_json"`)

	output := make(chan EventData, 1)

	if num, err := mix.TransformEventData(input, output); err == nil {
		t.Error("Expected error on bad json")
	} else if num != 1 {
		t.Errorf("Expected 1 record, got %d", num)
	}

	event := <-output
	expected := []struct {
		Name  string
		Value interface{}
	}{
		{"event", "a"},
		{"a", "1"},
	}

	for _, e := range expected {
		if v, ok := event[e.Name]; !ok || v != e.Value {
			t.Errorf("bad value: expected %s=(%v) got %s=(%v)", e.Name, e.Value,
				e.Name, v)
		}
	}
}

func TestExportDate(t *testing.T) {
	// TODO: write me
}

func BenchmarkTransformEventData(b *testing.B) {
	mix := New("product", "", "")
	input := strings.NewReader(
		strings.Repeat(`{"event": "a2", "properties": {"a": null, "b": "b2", "c": true, "d": ["foo"]}}`, b.N))
	output := make(chan EventData)

	b.ResetTimer()

	go mix.TransformEventData(input, output)
	for i := 0; i < b.N; i++ {
		<-output
	}
	close(output)
}
<commit_msg>Add test to check output of unix time extraction.<commit_after>package mixpanel

import (
	"fmt"
	"strings"
	"testing"
	"time"
)

func TestAddSignature(t *testing.T) {
	// TODO: write me
}

func TestMakeArgs(t *testing.T) {
	mix := New("product", "key", "secret")
	date, _ := time.Parse("2006-01-02", "1999-12-31")
	args := mix.makeArgs(date)

	expected := [][]string{
		{"format", "json"},
		{"api_key", "key"},
		{"from_date", "1999-12-31"},
		{"to_date", "1999-12-31"},
	}

	for _, pair := range expected {
		if args[pair[0]][0] != pair[1] {
			t.Errorf("Expected %v, got %s", pair, args[pair[0]][0])
		}
	}
}

func TestTransformEventData(t *testing.T) {
	mix := New("product", "", "")
	input := strings.NewReader(`
{"event": "a0", "properties": {"a": null, "b": "b0", "c": true, "d": ["foo"]}}
{"event": "a1", "properties": {"a": null, "b": "b1", "c": true, "d": ["foo"]}}
{"event": "a2", "properties": {"a": null, "b": "b2", "c": true, "d": ["foo"]}}`)

	output := make(chan EventData)

	go func() {
		if num, err := mix.TransformEventData(input, output); err != nil {
			t.Errorf("raised error: %v", err)
		} else if num != 3 {
			t.Errorf("expected to see 3 records, saw %d", num)
		}
	}()

	for i := 0; i < 3; i++ {
		event := <-output

		expected := []struct {
			Name  string
			Value interface{}
		}{
			{"event", fmt.Sprintf("a%d", i)},
			{"a", nil},
			{"b", fmt.Sprintf("b%d", i)},
			{"c", true},
		}

		for _, e := range expected {
			if v, ok := event[e.Name]; !ok || v != e.Value {
				t.Errorf("bad value: expected %s=(%v) got %s=(%v)", e.Name, e.Value,
					e.Name, v)
			}
		}
	}
	close(output)
}

func TestTransformEventDataApiError(t *testing.T) {
	mix := New("product", "", "")
	input := strings.NewReader(`{"error": "some api error"}`)

	output := make(chan EventData)

	if num, err := mix.TransformEventData(input, output); err == nil {
		t.Error("Expected error on bad json")
	} else if err.Error() != "product: API error: some api error" {
		t.Errorf("Bad error string: '%s'", err.Error())
	} else if num != 0 {
		t.Errorf("Expected 0 records, got %d", num)
	}
}

func TestTransformEventDataBadJson(t *testing.T) {
	mix := New("product", "", "")
	input := strings.NewReader(`{"event": "a", "properties": {"a": "1"}}
{"event": "bad_json"`)

	output := make(chan EventData, 1)

	if num, err := mix.TransformEventData(input, output); err == nil {
		t.Error("Expected error on bad json")
	} else if num != 1 {
		t.Errorf("Expected 1 record, got %d", num)
	}

	event := <-output
	expected := []struct {
		Name  string
		Value interface{}
	}{
		{"event", "a"},
		{"a", "1"},
	}

	for _, e := range expected {
		if v, ok := event[e.Name]; !ok || v != e.Value {
			t.Errorf("bad value: expected %s=(%v) got %s=(%v)", e.Name, e.Value,
				e.Name, v)
		}
	}
}

func TestExtractUnixTimestamp(t *testing.T) {
	mix := New("product", "", "")
	input := strings.NewReader(`{"event": "a", "properties": {"time": 1095379200}}`)
	output := make(chan EventData, 1)

	if num, err := mix.TransformEventData(input, output); err != nil {
		t.Error("Got error on valid json: ", err)
	} else if num != 1 {
		t.Errorf("Expected 1 record, got %d", num)
	}

	event := <-output
	if v, ok := event[TimestampKey]; !ok || v != "2004-09-17 00:00:00" {
		t.Errorf("Got bad time value: %d, %s", event["time"], event[TimestampKey])
	}
}

func TestExportDate(t *testing.T) {
	// TODO: write me
}

func BenchmarkTransformEventData(b *testing.B) {
	mix := New("product", "", "")
	input := strings.NewReader(
		strings.Repeat(`{"event": "a2", "properties": {"a": null, "b": "b2", "c": true, "d": ["foo"]}}`, b.N))
	output := make(chan EventData)

	b.ResetTimer()

	go mix.TransformEventData(input, output)
	for i := 0; i < b.N; i++ {
		<-output
	}
	close(output)
}
<|endoftext|>


<commit_before>package lib

import (
	"bufio"
	"fmt"
	"os"
	"reflect"
	"strings"
)

type Logger struct {
	Games     []GameLog
	Players   []PlayerLog
	Stats     SlicedStatLog
	Directory string
}

type LoggerMessage struct {
	Players []PlayerLog
	Stats   SlicedStatLog
}

type LogEntry struct {
	Timestamp int64
	Game      Game
	Move      Message
}

type GameLog struct {
	ID           string
	Name         string
	File         *os.File
	Stats        SlicedStatLog
	LastMoveTime int64
}

type PlayerLog struct {
	ID    string
	Name  string
	Stats SlicedStatLog
}

type SlicedStatLog struct {
	Overall         StatLog
	Modes           []StatLog
	NumPlayers      []StatLog
	ModesAndPlayers [][]StatLog
}

type StatLog struct {
	Turns         int64
	TimedTurns    int64
	Plays         int64
	Bombs         int64
	Discards      int64
	Hints         int64
	NumberHints   int64
	ColorHints    int64
	BombsLosses   int64
	TurnsLosses   int64
	NoPlaysLosses int64
	TurnTime      int64
	GameTime      int64
	StartedGames  int64
	FinishedGames int64
	Scores        []int
}

func (l *Logger) Initialize() ([]*Game, string) {
	l.Games = make([]GameLog, 0, MaxStoredGames)
	l.Players = make([]PlayerLog, 0, MaxStoredGames*MaxPlayers)
	l.Stats = CreateEmptySlicedStatLog()

	err := os.Mkdir(l.Directory, os.ModeDir|os.ModePerm)
	if err != nil && !os.IsExist(err) {
		return make([]*Game, 0, 0), "Error creating log directory: " + err.Error()
	}

	dir, openError := os.Open(l.Directory)
	if openError != nil {
		return make([]*Game, 0, 0), "Error opening log directory: " + openError.Error()
	}

	names, readError := dir.Readdirnames(0)
	if readError != nil {
		return make([]*Game, 0, 0), "Error reading log directory: " + readError.Error()
	}

	games := make([]*Game, 0, MaxConcurrentGames)
	statusUpdates := 5
	filesPerUpdate := len(names) / statusUpdates
	filesProcessed := 0
	nextUpdate := filesPerUpdate

	for _, name := range names {
		if strings.Index(name, ".json") > -1 {
			file, fileError := os.Open(l.Directory + name)
			if fileError != nil {
				return make([]*Game, 0, 0), "Error opening log file " + name + ": " + fileError.Error()
			}
			scanner := bufio.NewScanner(file)
			var le LogEntry
			var decodeError string

			for scanner.Scan() {
				json := scanner.Text()
				le, decodeError = DecodeLogEntry(json)
				if decodeError != "" {
					return make([]*Game, 0, 0), "Error decoding log " + name + ": " + decodeError
				}
				l.LogMove(le.Game, le.Move, le.Timestamp, false)
			}

			if err := scanner.Err(); err != nil {
				return make([]*Game, 0, 0), "Error scanning log file " + name + ": " + err.Error()
			}
			defer file.Close()

			if le.Game.State == StateNotStarted || le.Game.State == StateStarted {
				games = append(games, &le.Game)
			}

			filesProcessed++
			if filesProcessed >= nextUpdate {
				fmt.Printf("Processing games is %d%% complete.\n", int(100.0*filesProcessed/len(names)))
				nextUpdate += filesPerUpdate
			}
		}
	}

	return games, ""
}

func (l *Logger) LogMove(g Game, m Message, t int64, writeToFile bool) string {
	// find or create game log
	gl, err := l.GetOrCreateGameLog(g)
	if err != "" {
		return "Error retrieving game log: " + err
	}

	// log move to game log
	if writeToFile {
		le := LogEntry{Timestamp: t, Game: g, Move: m}
		logError := gl.LogMove(le)
		if logError != "" {
			return "Error logging move: " + logError
		}
	}

	// figure out all the stats
	pl := l.GetOrCreatePlayerLog(m.Player, g)

	statList := l.GetOnePlayersStatList(g, gl, pl)
	allPlayersStatsList := l.GetAllPlayersStatList(g, gl)

	IncrementProperty("Turns", statList[:]...)

	if !g.IgnoreTime {
		IncrementProperty("TimedTurns", statList[:]...)
		IncreaseProperty("TurnTime", t-gl.LastMoveTime, statList[:]...)
	}

	if m.MoveType == MovePlay && m.Result == ResultPlay {
		IncrementProperty("Plays", statList[:]...)
	} else if m.MoveType == MovePlay && m.Result == ResultBomb {
		IncrementProperty("Bombs", statList[:]...)
	} else if m.MoveType == MoveDiscard {
		IncrementProperty("Discards", statList[:]...)
	} else if m.MoveType == MoveHint {
		IncrementProperty("Hints", statList[:]...)
		if m.HintInfoType == HintNumber {
			IncrementProperty("NumberHints", statList[:]...)
		} else {
			IncrementProperty("ColorHints", statList[:]...)
		}
	}

	if gl.Stats.Overall.StartedGames == 0 {
		IncrementProperty("StartedGames", allPlayersStatsList[:]...)
	}

	if g.State == StateBombedOut {
		IncrementProperty("BombsLosses", allPlayersStatsList[:]...)
	} else if g.State == StateDeckEmpty {
		IncrementProperty("TurnsLosses", allPlayersStatsList[:]...)
	} else if g.State == StateNoPlays {
		IncrementProperty("NoPlaysLosses", allPlayersStatsList[:]...)
	}

	if g.State == StateBombedOut || g.State == StateDeckEmpty || g.State == StateNoPlays || g.State == StatePerfect {
		IncrementProperty("FinishedGames", allPlayersStatsList[:]...)
		if !g.IgnoreTime {
			IncreaseProperty("GameTime", t-g.StartTime, allPlayersStatsList[:]...)
		}
		IncrementScore(g.Score(), allPlayersStatsList[:]...)

		defer gl.File.Close()
	}

	gl.LastMoveTime = t

	return ""
}

func (l *Logger) GetOrCreateGameLog(g Game) (*GameLog, string) {
	for index, _ := range l.Games {
		if l.Games[index].ID == g.ID {
			return &l.Games[index], ""
		}
	}

	gl := GameLog{ID: g.ID, Name: g.Name, Stats: SlicedStatLog{}}
	gl.Stats = CreateEmptySlicedStatLog()
	gl.LastMoveTime = g.StartTime
	filename := l.Directory + g.ID + ".json"
	file, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600)
	if err != nil {
		if os.IsNotExist(err) {
			file, err = os.Create(filename)
			if err != nil {
				return new(GameLog), "Error creating new log file: " + err.Error()
			}
		} else {
			return new(GameLog), "Error opening log file: " + err.Error()
		}
	}
	gl.File = file

	l.Games = append(l.Games, gl)
	return &l.Games[len(l.Games)-1], ""
}

func (l *Logger) GetOrCreatePlayerLog(p string, g Game) *PlayerLog {
	for index, _ := range l.Players {
		if l.Players[index].ID == p {
			return &l.Players[index]
		}
	}

	pl := PlayerLog{ID: p, Stats: SlicedStatLog{}}
	pl.Stats = CreateEmptySlicedStatLog()
	pl.Name = g.GetPlayerByGoogleID(p).Name
	l.Players = append(l.Players, pl)
	return &l.Players[len(l.Players)-1]
}

func (l *Logger) GetAllPlayersStatList(g Game, gl *GameLog) []*StatLog {
	m := g.Mode
	p := len(g.Players)
	sl := make([]*StatLog, 0, (p+2)*4)
	for _, player := range g.Players {
		pl := l.GetOrCreatePlayerLog(player.GoogleID, g)
		sl = append(sl, &pl.Stats.Overall, &pl.Stats.Modes[m], &pl.Stats.NumPlayers[p], &pl.Stats.ModesAndPlayers[m][p])
	}
	sl = append(sl, &l.Stats.Overall, &l.Stats.Modes[m], &l.Stats.NumPlayers[p], &l.Stats.ModesAndPlayers[m][p])
	sl = append(sl, &gl.Stats.Overall, &gl.Stats.Modes[m], &gl.Stats.NumPlayers[p], &gl.Stats.ModesAndPlayers[m][p])
	return sl
}

func (l *Logger) GetOnePlayersStatList(g Game, gl *GameLog, pl *PlayerLog) []*StatLog {
	m := g.Mode
	p := len(g.Players)
	sl := make([]*StatLog, 0, 3*4)

	sl = append(sl, &pl.Stats.Overall, &pl.Stats.Modes[m], &pl.Stats.NumPlayers[p], &pl.Stats.ModesAndPlayers[m][p])
	sl = append(sl, &l.Stats.Overall, &l.Stats.Modes[m], &l.Stats.NumPlayers[p], &l.Stats.ModesAndPlayers[m][p])
	sl = append(sl, &gl.Stats.Overall, &gl.Stats.Modes[m], &gl.Stats.NumPlayers[p], &gl.Stats.ModesAndPlayers[m][p])
	return sl
}

func (l *Logger) CreateStatsLog() Logger {
	lCopy := Logger{}
	lCopy = *l

	// clear the logging directory
	lCopy.Directory = ""

	// clear game file handlers

	newGames := make([]GameLog, len(l.Games), len(l.Games))
	for index, gl := range lCopy.Games {
		newGames[index] = GameLog{ID: gl.ID, Name: gl.Name, Stats: gl.Stats}
	}
	lCopy.Games = newGames
	return lCopy
}

func (gl *GameLog) LogMove(le LogEntry) string {
	json, encodeError := EncodeLogEntry(le)
	if encodeError != "" {
		return "Error encoding log entry to JSON: " + encodeError
	}
	_, err := gl.File.WriteString(json + "\n")
	if err != nil {
		return "Error writing log entry: " + err.Error()
	}
	return ""
}

func IncrementProperty(p string, stats ...*StatLog) {
	IncreaseProperty(p, 1, stats...)
}

func IncreaseProperty(p string, n int64, stats ...*StatLog) {
	for i, _ := range stats {
		statLog := stats[i]
		f := reflect.ValueOf(*statLog).FieldByName(p).Int()
		reflect.ValueOf(statLog).Elem().FieldByName(p).SetInt(f + n)
	}
}

func IncrementScore(n int, stats ...*StatLog) {
	for i := range stats {
		statLog := stats[i]
		if statLog.Scores == nil {
			statLog.Scores = make([]int, 31, 31)
		}
		statLog.Scores[n]++
	}
}

func CreateEmptySlicedStatLog() SlicedStatLog {
	ssl := SlicedStatLog{}

	ssl.Modes = make([]StatLog, Modes+1, Modes+1)
	ssl.NumPlayers = make([]StatLog, MaxPlayers+1, MaxPlayers+1)
	ssl.ModesAndPlayers = make([][]StatLog, Modes+1, Modes+1)

	for i := 0; i <= Modes; i++ {
		ssl.ModesAndPlayers[i] = make([]StatLog, MaxPlayers+1, MaxPlayers+1)
	}

	return ssl
}
<commit_msg>Added more verbose output when starting server<commit_after>package lib

import (
	"bufio"
	"fmt"
	"os"
	"reflect"
	"strings"
)

type Logger struct {
	Games     []GameLog
	Players   []PlayerLog
	Stats     SlicedStatLog
	Directory string
}

type LoggerMessage struct {
	Players []PlayerLog
	Stats   SlicedStatLog
}

type LogEntry struct {
	Timestamp int64
	Game      Game
	Move      Message
}

type GameLog struct {
	ID           string
	Name         string
	File         *os.File
	Stats        SlicedStatLog
	LastMoveTime int64
}

type PlayerLog struct {
	ID    string
	Name  string
	Stats SlicedStatLog
}

type SlicedStatLog struct {
	Overall         StatLog
	Modes           []StatLog
	NumPlayers      []StatLog
	ModesAndPlayers [][]StatLog
}

type StatLog struct {
	Turns         int64
	TimedTurns    int64
	Plays         int64
	Bombs         int64
	Discards      int64
	Hints         int64
	NumberHints   int64
	ColorHints    int64
	BombsLosses   int64
	TurnsLosses   int64
	NoPlaysLosses int64
	TurnTime      int64
	GameTime      int64
	StartedGames  int64
	FinishedGames int64
	Scores        []int
}

func (l *Logger) Initialize() ([]*Game, string) {
	l.Games = make([]GameLog, 0, MaxStoredGames)
	l.Players = make([]PlayerLog, 0, MaxStoredGames*MaxPlayers)
	l.Stats = CreateEmptySlicedStatLog()

	err := os.Mkdir(l.Directory, os.ModeDir|os.ModePerm)
	if err != nil && !os.IsExist(err) {
		return make([]*Game, 0, 0), "Error creating log directory: " + err.Error()
	}

	dir, openError := os.Open(l.Directory)
	if openError != nil {
		return make([]*Game, 0, 0), "Error opening log directory: " + openError.Error()
	}

	names, readError := dir.Readdirnames(0)
	if readError != nil {
		return make([]*Game, 0, 0), "Error reading log directory: " + readError.Error()
	}

	fmt.Printf("Found %'d games.\n", len(names))
	games := make([]*Game, 0, MaxConcurrentGames)
	statusUpdates := 10
	filesPerUpdate := len(names) / statusUpdates
	filesProcessed := 0
	nextUpdate := filesPerUpdate

	for _, name := range names {
		if strings.Index(name, ".json") > -1 {
			file, fileError := os.Open(l.Directory + name)
			if fileError != nil {
				return make([]*Game, 0, 0), "Error opening log file " + name + ": " + fileError.Error()
			}
			scanner := bufio.NewScanner(file)
			var le LogEntry
			var decodeError string

			for scanner.Scan() {
				json := scanner.Text()
				le, decodeError = DecodeLogEntry(json)
				if decodeError != "" {
					return make([]*Game, 0, 0), "Error decoding log " + name + ": " + decodeError
				}
				l.LogMove(le.Game, le.Move, le.Timestamp, false)
			}

			if err := scanner.Err(); err != nil {
				return make([]*Game, 0, 0), "Error scanning log file " + name + ": " + err.Error()
			}
			file.Close()

			if le.Game.State == StateNotStarted || le.Game.State == StateStarted {
				games = append(games, &le.Game)
			}

			filesProcessed++
			if filesProcessed >= nextUpdate {
				fmt.Printf("Processed %'d games. %d%% complete.\n", filesProcessed, int(100.0*filesProcessed/len(names)))
				nextUpdate += filesPerUpdate
			}
		}
	}

	return games, ""
}

func (l *Logger) LogMove(g Game, m Message, t int64, writeToFile bool) string {
	// find or create game log
	gl, err := l.GetOrCreateGameLog(g)
	if err != "" {
		return "Error retrieving game log: " + err
	}

	// log move to game log
	if writeToFile {
		le := LogEntry{Timestamp: t, Game: g, Move: m}
		logError := gl.LogMove(le)
		if logError != "" {
			return "Error logging move: " + logError
		}
	}

	// figure out all the stats
	pl := l.GetOrCreatePlayerLog(m.Player, g)

	statList := l.GetOnePlayersStatList(g, gl, pl)
	allPlayersStatsList := l.GetAllPlayersStatList(g, gl)

	IncrementProperty("Turns", statList[:]...)

	if !g.IgnoreTime {
		IncrementProperty("TimedTurns", statList[:]...)
		IncreaseProperty("TurnTime", t-gl.LastMoveTime, statList[:]...)
	}

	if m.MoveType == MovePlay && m.Result == ResultPlay {
		IncrementProperty("Plays", statList[:]...)
	} else if m.MoveType == MovePlay && m.Result == ResultBomb {
		IncrementProperty("Bombs", statList[:]...)
	} else if m.MoveType == MoveDiscard {
		IncrementProperty("Discards", statList[:]...)
	} else if m.MoveType == MoveHint {
		IncrementProperty("Hints", statList[:]...)
		if m.HintInfoType == HintNumber {
			IncrementProperty("NumberHints", statList[:]...)
		} else {
			IncrementProperty("ColorHints", statList[:]...)
		}
	}

	if gl.Stats.Overall.StartedGames == 0 {
		IncrementProperty("StartedGames", allPlayersStatsList[:]...)
	}

	if g.State == StateBombedOut {
		IncrementProperty("BombsLosses", allPlayersStatsList[:]...)
	} else if g.State == StateDeckEmpty {
		IncrementProperty("TurnsLosses", allPlayersStatsList[:]...)
	} else if g.State == StateNoPlays {
		IncrementProperty("NoPlaysLosses", allPlayersStatsList[:]...)
	}

	if g.State == StateBombedOut || g.State == StateDeckEmpty || g.State == StateNoPlays || g.State == StatePerfect {
		IncrementProperty("FinishedGames", allPlayersStatsList[:]...)
		if !g.IgnoreTime {
			IncreaseProperty("GameTime", t-g.StartTime, allPlayersStatsList[:]...)
		}
		IncrementScore(g.Score(), allPlayersStatsList[:]...)

		defer gl.File.Close()
	}

	gl.LastMoveTime = t

	return ""
}

func (l *Logger) GetOrCreateGameLog(g Game) (*GameLog, string) {
	for index, _ := range l.Games {
		if l.Games[index].ID == g.ID {
			return &l.Games[index], ""
		}
	}

	gl := GameLog{ID: g.ID, Name: g.Name, Stats: SlicedStatLog{}}
	gl.Stats = CreateEmptySlicedStatLog()
	gl.LastMoveTime = g.StartTime
	filename := l.Directory + g.ID + ".json"
	file, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600)
	if err != nil {
		if os.IsNotExist(err) {
			file, err = os.Create(filename)
			if err != nil {
				return new(GameLog), "Error creating new log file: " + err.Error()
			}
		} else {
			return new(GameLog), "Error opening log file: " + err.Error()
		}
	}
	gl.File = file

	l.Games = append(l.Games, gl)
	return &l.Games[len(l.Games)-1], ""
}

func (l *Logger) GetOrCreatePlayerLog(p string, g Game) *PlayerLog {
	for index, _ := range l.Players {
		if l.Players[index].ID == p {
			return &l.Players[index]
		}
	}

	pl := PlayerLog{ID: p, Stats: SlicedStatLog{}}
	pl.Stats = CreateEmptySlicedStatLog()
	pl.Name = g.GetPlayerByGoogleID(p).Name
	l.Players = append(l.Players, pl)
	return &l.Players[len(l.Players)-1]
}

func (l *Logger) GetAllPlayersStatList(g Game, gl *GameLog) []*StatLog {
	m := g.Mode
	p := len(g.Players)
	sl := make([]*StatLog, 0, (p+2)*4)
	for _, player := range g.Players {
		pl := l.GetOrCreatePlayerLog(player.GoogleID, g)
		sl = append(sl, &pl.Stats.Overall, &pl.Stats.Modes[m], &pl.Stats.NumPlayers[p], &pl.Stats.ModesAndPlayers[m][p])
	}
	sl = append(sl, &l.Stats.Overall, &l.Stats.Modes[m], &l.Stats.NumPlayers[p], &l.Stats.ModesAndPlayers[m][p])
	sl = append(sl, &gl.Stats.Overall, &gl.Stats.Modes[m], &gl.Stats.NumPlayers[p], &gl.Stats.ModesAndPlayers[m][p])
	return sl
}

func (l *Logger) GetOnePlayersStatList(g Game, gl *GameLog, pl *PlayerLog) []*StatLog {
	m := g.Mode
	p := len(g.Players)
	sl := make([]*StatLog, 0, 3*4)

	sl = append(sl, &pl.Stats.Overall, &pl.Stats.Modes[m], &pl.Stats.NumPlayers[p], &pl.Stats.ModesAndPlayers[m][p])
	sl = append(sl, &l.Stats.Overall, &l.Stats.Modes[m], &l.Stats.NumPlayers[p], &l.Stats.ModesAndPlayers[m][p])
	sl = append(sl, &gl.Stats.Overall, &gl.Stats.Modes[m], &gl.Stats.NumPlayers[p], &gl.Stats.ModesAndPlayers[m][p])
	return sl
}

func (l *Logger) CreateStatsLog() Logger {
	lCopy := Logger{}
	lCopy = *l

	// clear the logging directory
	lCopy.Directory = ""

	// clear game file handlers

	newGames := make([]GameLog, len(l.Games), len(l.Games))
	for index, gl := range lCopy.Games {
		newGames[index] = GameLog{ID: gl.ID, Name: gl.Name, Stats: gl.Stats}
	}
	lCopy.Games = newGames
	return lCopy
}

func (gl *GameLog) LogMove(le LogEntry) string {
	json, encodeError := EncodeLogEntry(le)
	if encodeError != "" {
		return "Error encoding log entry to JSON: " + encodeError
	}
	_, err := gl.File.WriteString(json + "\n")
	if err != nil {
		return "Error writing log entry: " + err.Error()
	}
	return ""
}

func IncrementProperty(p string, stats ...*StatLog) {
	IncreaseProperty(p, 1, stats...)
}

func IncreaseProperty(p string, n int64, stats ...*StatLog) {
	for i, _ := range stats {
		statLog := stats[i]
		f := reflect.ValueOf(*statLog).FieldByName(p).Int()
		reflect.ValueOf(statLog).Elem().FieldByName(p).SetInt(f + n)
	}
}

func IncrementScore(n int, stats ...*StatLog) {
	for i := range stats {
		statLog := stats[i]
		if statLog.Scores == nil {
			statLog.Scores = make([]int, 31, 31)
		}
		statLog.Scores[n]++
	}
}

func CreateEmptySlicedStatLog() SlicedStatLog {
	ssl := SlicedStatLog{}

	ssl.Modes = make([]StatLog, Modes+1, Modes+1)
	ssl.NumPlayers = make([]StatLog, MaxPlayers+1, MaxPlayers+1)
	ssl.ModesAndPlayers = make([][]StatLog, Modes+1, Modes+1)

	for i := 0; i <= Modes; i++ {
		ssl.ModesAndPlayers[i] = make([]StatLog, MaxPlayers+1, MaxPlayers+1)
	}

	return ssl
}
<|endoftext|>


<commit_before>// Copyright 2012, Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package proto

import (
	"testing"

	"github.com/youtube/vitess/go/bson"
	tproto "github.com/youtube/vitess/go/vt/tabletserver/proto"
	"github.com/youtube/vitess/go/vt/topo"
)

type reflectSessionParams struct {
	TabletType topo.TabletType
}

type badSessionParams struct {
	Extra      int
	TabletType topo.TabletType
}

func TestSessionParams(t *testing.T) {
	reflected, err := bson.Marshal(&reflectSessionParams{topo.TabletType("master")})
	if err != nil {
		t.Error(err)
	}
	want := string(reflected)

	custom := SessionParams{topo.TabletType("master")}
	encoded, err := bson.Marshal(&custom)
	if err != nil {
		t.Error(err)
	}
	got := string(encoded)
	if want != got {
		t.Errorf("want\n%#v, got\n%#v", want, got)
	}

	var unmarshalled SessionParams
	err = bson.Unmarshal(encoded, &unmarshalled)
	if err != nil {
		t.Error(err)
	}
	if custom != unmarshalled {
		t.Errorf("want %v, got %#v", custom, unmarshalled)
	}

	unexpected, err := bson.Marshal(&badSessionParams{})
	if err != nil {
		t.Error(err)
	}
	err = bson.Unmarshal(unexpected, &unmarshalled)
	want = "Unrecognized tag Extra"
	if err == nil || want != err.Error() {
		t.Errorf("want %v, got %v", want, err)
	}
}

type reflectSession struct {
	SessionId int64
}

type badSession struct {
	Extra     int
	SessionId int64
}

func TestSession(t *testing.T) {
	reflected, err := bson.Marshal(&reflectSession{1})
	if err != nil {
		t.Error(err)
	}
	want := string(reflected)

	custom := Session{1}
	encoded, err := bson.Marshal(&custom)
	if err != nil {
		t.Error(err)
	}
	got := string(encoded)
	if want != got {
		t.Errorf("want\n%#v, got\n%#v", want, got)
	}

	var unmarshalled Session
	err = bson.Unmarshal(encoded, &unmarshalled)
	if err != nil {
		t.Error(err)
	}
	if custom != unmarshalled {
		t.Errorf("want %v, got %#v", custom, unmarshalled)
	}

	unexpected, err := bson.Marshal(&badSession{})
	if err != nil {
		t.Error(err)
	}
	err = bson.Unmarshal(unexpected, &unmarshalled)
	want = "Unrecognized tag Extra"
	if err == nil || want != err.Error() {
		t.Errorf("want %v, got %v", want, err)
	}
}

type reflectQueryShard struct {
	Sql           string
	BindVariables map[string]interface{}
	SessionId     int64
	Keyspace      string
	Shards        []string
}

type badQueryShard struct {
	Extra         int
	Sql           string
	BindVariables map[string]interface{}
	SessionId     int64
	Keyspace      string
	Shards        []string
}

func TestQueryShard(t *testing.T) {
	reflected, err := bson.Marshal(&reflectQueryShard{
		Sql:           "query",
		BindVariables: map[string]interface{}{"val": int64(1)},
		SessionId:     1,
		Keyspace:      "keyspace",
		Shards:        []string{"shard1", "shard2"},
	})
	if err != nil {
		t.Error(err)
	}
	want := string(reflected)

	custom := QueryShard{
		Sql:           "query",
		BindVariables: map[string]interface{}{"val": int64(1)},
		SessionId:     1,
		Keyspace:      "keyspace",
		Shards:        []string{"shard1", "shard2"},
	}
	encoded, err := bson.Marshal(&custom)
	if err != nil {
		t.Error(err)
	}
	got := string(encoded)
	if want != got {
		t.Errorf("want\n%#v, got\n%#v", want, got)
	}

	var unmarshalled QueryShard
	err = bson.Unmarshal(encoded, &unmarshalled)
	if err != nil {
		t.Error(err)
	}
	if custom.Sql != unmarshalled.Sql {
		t.Errorf("want %v, got %v", custom.Sql, unmarshalled.Sql)
	}
	if custom.SessionId != unmarshalled.SessionId {
		t.Errorf("want %v, got %v", custom.SessionId, unmarshalled.SessionId)
	}
	if custom.Keyspace != unmarshalled.Keyspace {
		t.Errorf("want %v, got %v", custom.Keyspace, unmarshalled.Keyspace)
	}
	if custom.BindVariables["val"].(int64) != unmarshalled.BindVariables["val"].(int64) {
		t.Errorf("want %v, got %v", custom.BindVariables["val"], unmarshalled.BindVariables["val"])
	}
	if custom.Shards[0] != unmarshalled.Shards[0] {
		t.Errorf("want %v, got %v", custom.Shards[0], unmarshalled.Shards[0])
	}
	if custom.Shards[1] != unmarshalled.Shards[1] {
		t.Errorf("want %v, got %v", custom.Shards[1], unmarshalled.Shards[1])
	}

	unexpected, err := bson.Marshal(&badQueryShard{})
	if err != nil {
		t.Error(err)
	}
	err = bson.Unmarshal(unexpected, &unmarshalled)
	want = "Unrecognized tag Extra"
	if err == nil || want != err.Error() {
		t.Errorf("want %v, got %v", want, err)
	}
}

type reflectBoundQuery struct {
	Sql           string
	BindVariables map[string]interface{}
}

type reflectBatchQueryShard struct {
	Queries   []reflectBoundQuery
	SessionId int64
	Keyspace  string
	Shards    []string
}

type badBatchQueryShard struct {
	Extra     int
	Queries   []reflectBoundQuery
	SessionId int64
	Keyspace  string
	Shards    []string
}

func TestBatchQueryShard(t *testing.T) {
	reflected, err := bson.Marshal(&reflectBatchQueryShard{
		Queries: []reflectBoundQuery{{
			Sql:           "query",
			BindVariables: map[string]interface{}{"val": int64(1)},
		}},
		SessionId: 1,
		Keyspace:  "keyspace",
		Shards:    []string{"shard1", "shard2"},
	})
	if err != nil {
		t.Error(err)
	}
	want := string(reflected)

	custom := BatchQueryShard{
		Queries: []tproto.BoundQuery{{
			Sql:           "query",
			BindVariables: map[string]interface{}{"val": int64(1)},
		}},
		SessionId: 1,
		Keyspace:  "keyspace",
		Shards:    []string{"shard1", "shard2"},
	}
	encoded, err := bson.Marshal(&custom)
	if err != nil {
		t.Error(err)
	}
	got := string(encoded)
	if want != got {
		t.Errorf("want\n%#v, got\n%#v", want, got)
	}

	var unmarshalled BatchQueryShard
	err = bson.Unmarshal(encoded, &unmarshalled)
	if err != nil {
		t.Error(err)
	}
	if custom.Queries[0].Sql != unmarshalled.Queries[0].Sql {
		t.Errorf("want %v, got %v", custom.Queries[0].Sql, unmarshalled.Queries[0].Sql)
	}
	if custom.SessionId != unmarshalled.SessionId {
		t.Errorf("want %v, got %v", custom.SessionId, unmarshalled.SessionId)
	}
	if custom.Keyspace != unmarshalled.Keyspace {
		t.Errorf("want %v, got %v", custom.Keyspace, unmarshalled.Keyspace)
	}
	if custom.Queries[0].BindVariables["val"].(int64) != unmarshalled.Queries[0].BindVariables["val"].(int64) {
		t.Errorf("want %v, got %v", custom.Queries[0].BindVariables["val"], unmarshalled.Queries[0].BindVariables["val"])
	}
	if custom.Shards[0] != unmarshalled.Shards[0] {
		t.Errorf("want %v, got %v", custom.Shards[0], unmarshalled.Shards[0])
	}
	if custom.Shards[1] != unmarshalled.Shards[1] {
		t.Errorf("want %v, got %v", custom.Shards[1], unmarshalled.Shards[1])
	}

	unexpected, err := bson.Marshal(&badBatchQueryShard{})
	if err != nil {
		t.Error(err)
	}
	err = bson.Unmarshal(unexpected, &unmarshalled)
	want = "Unrecognized tag Extra"
	if err == nil || want != err.Error() {
		t.Errorf("want %v, got %v", want, err)
	}
}

type badTypeBatchQueryShard struct {
	Queries   string
	SessionId int64
	Keyspace  string
	Shards    []string
}

func TestBatchQueryShardBadType(t *testing.T) {
	unexpected, err := bson.Marshal(&badTypeBatchQueryShard{})
	if err != nil {
		t.Error(err)
	}
	var unmarshalled BatchQueryShard
	err = bson.Unmarshal(unexpected, &unmarshalled)
	want := "Unexpected data type 5 for Queries"
	if err == nil || want != err.Error() {
		t.Errorf("want %v, got %v", want, err)
	}
}
<commit_msg>vtgate_proto tests<commit_after>// Copyright 2012, Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package proto

import (
	"reflect"
	"testing"

	"github.com/youtube/vitess/go/bson"
	mproto "github.com/youtube/vitess/go/mysql/proto"
	"github.com/youtube/vitess/go/sqltypes"
	tproto "github.com/youtube/vitess/go/vt/tabletserver/proto"
	"github.com/youtube/vitess/go/vt/topo"
)

type reflectSession struct {
	InTransaction bool
	ShardSessions []*ShardSession
}

type badSession struct {
	Extra         int
	InTransaction bool
	ShardSessions []*ShardSession
}

func TestSession(t *testing.T) {
	reflected, err := bson.Marshal(&reflectSession{
		InTransaction: true,
		ShardSessions: []*ShardSession{{
			Keyspace:      "a",
			Shard:         "0",
			TabletType:    topo.TabletType("replica"),
			TransactionId: 1,
		}, {
			Keyspace:      "b",
			Shard:         "1",
			TabletType:    topo.TabletType("master"),
			TransactionId: 2,
		}},
	})
	if err != nil {
		t.Error(err)
	}
	want := string(reflected)

	custom := Session{
		InTransaction: true,
		ShardSessions: []*ShardSession{{
			Keyspace:      "a",
			Shard:         "0",
			TabletType:    topo.TabletType("replica"),
			TransactionId: 1,
		}, {
			Keyspace:      "b",
			Shard:         "1",
			TabletType:    topo.TabletType("master"),
			TransactionId: 2,
		}},
	}
	encoded, err := bson.Marshal(&custom)
	if err != nil {
		t.Error(err)
	}
	got := string(encoded)
	if want != got {
		t.Errorf("want\n%#v, got\n%#v", want, got)
	}

	var unmarshalled Session
	err = bson.Unmarshal(encoded, &unmarshalled)
	if err != nil {
		t.Error(err)
	}
	if !reflect.DeepEqual(custom, unmarshalled) {
		t.Errorf("want \n%#v, got \n%#v", custom, unmarshalled)
	}

	unexpected, err := bson.Marshal(&badSession{})
	if err != nil {
		t.Error(err)
	}
	err = bson.Unmarshal(unexpected, &unmarshalled)
	want = "Unrecognized tag Extra"
	if err == nil || want != err.Error() {
		t.Errorf("want %v, got %v", want, err)
	}
}

type reflectQueryShard struct {
	Sql           string
	BindVariables map[string]interface{}
	Keyspace      string
	TabletType    topo.TabletType
	Shards        []string
	Sessn         *Session
}

type badQueryShard struct {
	Extra         int
	Sql           string
	BindVariables map[string]interface{}
	Keyspace      string
	TabletType    topo.TabletType
	Shards        []string
	Sessn         *Session
}

func TestQueryShard(t *testing.T) {
	reflected, err := bson.Marshal(&reflectQueryShard{
		Sql:           "query",
		BindVariables: map[string]interface{}{"val": int64(1)},
		Keyspace:      "keyspace",
		TabletType:    topo.TabletType("replica"),
		Shards:        []string{"shard1", "shard2"},
		Sessn: &Session{InTransaction: true,
			ShardSessions: []*ShardSession{{
				Keyspace:      "a",
				Shard:         "0",
				TabletType:    topo.TabletType("replica"),
				TransactionId: 1,
			}, {
				Keyspace:      "b",
				Shard:         "1",
				TabletType:    topo.TabletType("master"),
				TransactionId: 2,
			}},
		},
	})
	if err != nil {
		t.Error(err)
	}
	want := string(reflected)

	custom := QueryShard{
		Sql:           "query",
		BindVariables: map[string]interface{}{"val": int64(1)},
		Keyspace:      "keyspace",
		TabletType:    topo.TabletType("replica"),
		Shards:        []string{"shard1", "shard2"},
		Sessn: &Session{InTransaction: true,
			ShardSessions: []*ShardSession{{
				Keyspace:      "a",
				Shard:         "0",
				TabletType:    topo.TabletType("replica"),
				TransactionId: 1,
			}, {
				Keyspace:      "b",
				Shard:         "1",
				TabletType:    topo.TabletType("master"),
				TransactionId: 2,
			}},
		},
	}
	encoded, err := bson.Marshal(&custom)
	if err != nil {
		t.Error(err)
	}
	got := string(encoded)
	if want != got {
		t.Errorf("want\n%#v, got\n%#v", want, got)
	}

	var unmarshalled QueryShard
	err = bson.Unmarshal(encoded, &unmarshalled)
	if err != nil {
		t.Error(err)
	}
	if !reflect.DeepEqual(custom, unmarshalled) {
		t.Errorf("want \n%#v, got \n%#v", custom, unmarshalled)
	}

	unexpected, err := bson.Marshal(&badQueryShard{})
	if err != nil {
		t.Error(err)
	}
	err = bson.Unmarshal(unexpected, &unmarshalled)
	want = "Unrecognized tag Extra"
	if err == nil || want != err.Error() {
		t.Errorf("want %v, got %v", want, err)
	}
}

func TestQueryResult(t *testing.T) {
	// We can't do the reflection test because bson
	// doesn't do it correctly for embedded fields.
	want := "\\\x01\x00\x00" +
		"\x04Fields\x00*\x00\x00\x00\x030\x00\"\x00\x00\x00" +
		"\x05Name\x00\x04\x00\x00\x00\x00name" +
		"\x12Type\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
		"\x12RowsAffected\x00\x02\x00\x00\x00\x00\x00\x00\x00" +
		"\x12InsertId\x00\x03\x00\x00\x00\x00\x00\x00\x00" +
		"\x04Rows\x00 \x00\x00\x00" +
		"\x040\x00\x18\x00\x00\x00" +
		"\x050\x00\x01\x00\x00\x00\x001" +
		"\x051\x00\x02\x00\x00\x00\x00aa" +
		"\x00\x00" +
		"\x03Sessn\x00\xd0\x00\x00\x00" +
		"\bInTransaction\x00\x01" +
		"\x04ShardSessions\x00\xac\x00\x00\x00" +
		"\x030\x00Q\x00\x00\x00" +
		"\x05Keyspace\x00\x01\x00\x00\x00\x00a" +
		"\x05Shard\x00\x01\x00\x00\x00\x000" +
		"\x05TabletType\x00\a\x00\x00\x00\x00replica" +
		"\x12TransactionId\x00\x01\x00\x00\x00\x00\x00\x00\x00" +
		"\x00" +
		"\x031\x00P\x00\x00\x00" +
		"\x05Keyspace\x00\x01\x00\x00\x00\x00b" +
		"\x05Shard\x00\x01\x00\x00\x00\x001" +
		"\x05TabletType\x00\x06\x00\x00\x00\x00master" +
		"\x12TransactionId\x00\x02\x00\x00\x00\x00\x00\x00\x00" +
		"\x00\x00\x00\x00"

	custom := QueryResult{
		QueryResult: mproto.QueryResult{
			Fields:       []mproto.Field{{"name", 1}},
			RowsAffected: 2,
			InsertId:     3,
			Rows: [][]sqltypes.Value{
				{{sqltypes.String("1")}, {sqltypes.String("aa")}},
			},
		},
		Sessn: &Session{InTransaction: true,
			ShardSessions: []*ShardSession{{
				Keyspace:      "a",
				Shard:         "0",
				TabletType:    topo.TabletType("replica"),
				TransactionId: 1,
			}, {
				Keyspace:      "b",
				Shard:         "1",
				TabletType:    topo.TabletType("master"),
				TransactionId: 2,
			}},
		},
	}
	encoded, err := bson.Marshal(&custom)
	if err != nil {
		t.Error(err)
	}
	got := string(encoded)
	if want != got {
		t.Errorf("want\n%#v, got\n%#v", want, got)
	}

	var unmarshalled QueryResult
	err = bson.Unmarshal(encoded, &unmarshalled)
	if err != nil {
		t.Error(err)
	}
	if !reflect.DeepEqual(custom, unmarshalled) {
		t.Errorf("want \n%#v, got \n%#v", custom, unmarshalled)
	}
}

type reflectBoundQuery struct {
	Sql           string
	BindVariables map[string]interface{}
}

type reflectBatchQueryShard struct {
	Queries    []reflectBoundQuery
	Keyspace   string
	TabletType topo.TabletType
	Shards     []string
	Sessn      *Session
}

type badBatchQueryShard struct {
	Extra      int
	Queries    []reflectBoundQuery
	Keyspace   string
	TabletType topo.TabletType
	Shards     []string
	Sessn      *Session
}

func TestBatchQueryShard(t *testing.T) {
	reflected, err := bson.Marshal(&reflectBatchQueryShard{
		Queries: []reflectBoundQuery{{
			Sql:           "query",
			BindVariables: map[string]interface{}{"val": int64(1)},
		}},
		Keyspace: "keyspace",
		Shards:   []string{"shard1", "shard2"},
		Sessn: &Session{InTransaction: true,
			ShardSessions: []*ShardSession{{
				Keyspace:      "a",
				Shard:         "0",
				TabletType:    topo.TabletType("replica"),
				TransactionId: 1,
			}, {
				Keyspace:      "b",
				Shard:         "1",
				TabletType:    topo.TabletType("master"),
				TransactionId: 2,
			}},
		},
	})
	if err != nil {
		t.Error(err)
	}
	want := string(reflected)

	custom := BatchQueryShard{
		Queries: []tproto.BoundQuery{{
			Sql:           "query",
			BindVariables: map[string]interface{}{"val": int64(1)},
		}},
		Keyspace: "keyspace",
		Shards:   []string{"shard1", "shard2"},
		Sessn: &Session{InTransaction: true,
			ShardSessions: []*ShardSession{{
				Keyspace:      "a",
				Shard:         "0",
				TabletType:    topo.TabletType("replica"),
				TransactionId: 1,
			}, {
				Keyspace:      "b",
				Shard:         "1",
				TabletType:    topo.TabletType("master"),
				TransactionId: 2,
			}},
		},
	}
	encoded, err := bson.Marshal(&custom)
	if err != nil {
		t.Error(err)
	}
	got := string(encoded)
	if want != got {
		t.Errorf("want\n%#v, got\n%#v", want, got)
	}

	var unmarshalled BatchQueryShard
	err = bson.Unmarshal(encoded, &unmarshalled)
	if err != nil {
		t.Error(err)
	}
	if !reflect.DeepEqual(custom, unmarshalled) {
		t.Errorf("want \n%#v, got \n%#v", custom, unmarshalled)
	}

	unexpected, err := bson.Marshal(&badBatchQueryShard{})
	if err != nil {
		t.Error(err)
	}
	err = bson.Unmarshal(unexpected, &unmarshalled)
	want = "Unrecognized tag Extra"
	if err == nil || want != err.Error() {
		t.Errorf("want %v, got %v", want, err)
	}
}

type badTypeBatchQueryShard struct {
	Queries    string
	Keyspace   string
	TabletType topo.TabletType
	Shards     []string
	Sessn      *Session
}

func TestBatchQueryShardBadType(t *testing.T) {
	unexpected, err := bson.Marshal(&badTypeBatchQueryShard{})
	if err != nil {
		t.Error(err)
	}
	var unmarshalled BatchQueryShard
	err = bson.Unmarshal(unexpected, &unmarshalled)
	want := "Unexpected data type 5 for Queries"
	if err == nil || want != err.Error() {
		t.Errorf("want %v, got %v", want, err)
	}
}
<|endoftext|>


<commit_before>package stripe

import "encoding/json"

// List of values that BalanceTransactionStatus can take.
const (
	BalanceTransactionStatusAvailable BalanceTransactionStatus = "available"
	BalanceTransactionStatusPending   BalanceTransactionStatus = "pending"
)

// BalanceTransactionType is the list of allowed values for the balance transaction's type.
type BalanceTransactionType string

// List of values that BalanceTransactionType can take.
const (
	BalanceTransactionTypeAdjustment                      BalanceTransactionType = "adjustment"
	BalanceTransactionTypeAnticipationRepayment           BalanceTransactionType = "anticipation_repayment"
	BalanceTransactionTypeApplicationFee                  BalanceTransactionType = "application_fee"
	BalanceTransactionTypeApplicationFeeRefund            BalanceTransactionType = "application_fee_refund"
	BalanceTransactionTypeCharge                          BalanceTransactionType = "charge"
	BalanceTransactionTypeIssuingAuthorizationHold        BalanceTransactionType = "issuing_authorization_hold"
	BalanceTransactionTypeIssuingAuthorizationRelease     BalanceTransactionType = "issuing_authorization_release"
	BalanceTransactionTypeIssuingAuthorizationDispute     BalanceTransactionType = "issuing_dispute"
	BalanceTransactionTypeIssuingAuthorizationTransaction BalanceTransactionType = "issuing_transaction"
	BalanceTransactionTypePayment                         BalanceTransactionType = "payment"
	BalanceTransactionTypePaymentFailureRefund            BalanceTransactionType = "payment_failure_refund"
	BalanceTransactionTypePaymentRefund                   BalanceTransactionType = "payment_refund"
	BalanceTransactionTypePayout                          BalanceTransactionType = "payout"
	BalanceTransactionTypePayoutCancel                    BalanceTransactionType = "payout_cancel"
	BalanceTransactionTypePayoutFailure                   BalanceTransactionType = "payout_failure"
	BalanceTransactionTypeRefund                          BalanceTransactionType = "refund"
	BalanceTransactionTypeStripeFee                       BalanceTransactionType = "stripe_fee"
	BalanceTransactionTypeTransfer                        BalanceTransactionType = "transfer"
	BalanceTransactionTypeTransferRefund                  BalanceTransactionType = "transfer_refund"
)

// BalanceTransactionSourceType consts represent valid balance transaction sources.
type BalanceTransactionSourceType string

// List of values that BalanceTransactionSourceType can take.
const (
	BalanceTransactionSourceTypeApplicationFee       BalanceTransactionSourceType = "application_fee"
	BalanceTransactionSourceTypeCharge               BalanceTransactionSourceType = "charge"
	BalanceTransactionSourceTypeDispute              BalanceTransactionSourceType = "dispute"
	BalanceTransactionSourceTypeIssuingAuthorization BalanceTransactionSourceType = "issuing.authorization"
	BalanceTransactionSourceTypeIssuingTransaction   BalanceTransactionSourceType = "issuing.transaction"
	BalanceTransactionSourceTypePayout               BalanceTransactionSourceType = "payout"
	BalanceTransactionSourceTypeRefund               BalanceTransactionSourceType = "refund"
	BalanceTransactionSourceTypeReversal             BalanceTransactionSourceType = "reversal"
	BalanceTransactionSourceTypeTransfer             BalanceTransactionSourceType = "transfer"
)

// BalanceTransactionReportingCategory represents reporting categories for balance transactions.
type BalanceTransactionReportingCategory string

// List of values that BalanceTransactionReportingCategory can take.
const (
	BalanceTransactionReportingCategoryAdvance                     BalanceTransactionReportingCategory = "advance"
	BalanceTransactionReportingCategoryAdvanceFunding              BalanceTransactionReportingCategory = "advance_funding"
	BalanceTransactionReportingCategoryCharge                      BalanceTransactionReportingCategory = "charge"
	BalanceTransactionReportingCategoryChargeFailure               BalanceTransactionReportingCategory = "charge_failure"
	BalanceTransactionReportingCategoryConnectCollectionTransfer   BalanceTransactionReportingCategory = "connect_collection_transfer"
	BalanceTransactionReportingCategoryConnectReservedFunds        BalanceTransactionReportingCategory = "connect_reserved_funds"
	BalanceTransactionReportingCategoryDispute                     BalanceTransactionReportingCategory = "dispute"
	BalanceTransactionReportingCategoryDisputeReversal             BalanceTransactionReportingCategory = "dispute_reversal"
	BalanceTransactionReportingCategoryFee                         BalanceTransactionReportingCategory = "fee"
	BalanceTransactionReportingCategoryIssuingAuthorizationHold    BalanceTransactionReportingCategory = "issuing_authorization_hold"
	BalanceTransactionReportingCategoryIssuingAuthorizationRelease BalanceTransactionReportingCategory = "issuing_authorization_release"
	BalanceTransactionReportingCategoryIssuingTransaction          BalanceTransactionReportingCategory = "issuing_transaction"
	BalanceTransactionReportingCategoryOtherAdjustment             BalanceTransactionReportingCategory = "other_adjustment"
	BalanceTransactionReportingCategoryPartialCaptureReversal      BalanceTransactionReportingCategory = "partial_capture_reversal"
	BalanceTransactionReportingCategoryPayout                      BalanceTransactionReportingCategory = "payout"
	BalanceTransactionReportingCategoryPayoutReversal              BalanceTransactionReportingCategory = "payout_reversal"
	BalanceTransactionReportingCategoryPlatformEarning             BalanceTransactionReportingCategory = "platform_earning"
	BalanceTransactionReportingCategoryPlatformEarningRefund       BalanceTransactionReportingCategory = "platform_earning_refund"
	BalanceTransactionReportingCategoryRefund                      BalanceTransactionReportingCategory = "refund"
	BalanceTransactionReportingCategoryRefundFailure               BalanceTransactionReportingCategory = "refund_failure"
	BalanceTransactionReportingCategoryRiskReservedFunds           BalanceTransactionReportingCategory = "risk_reserved_funds"
	BalanceTransactionReportingCategoryTax                         BalanceTransactionReportingCategory = "tax"
	BalanceTransactionReportingCategoryTopup                       BalanceTransactionReportingCategory = "topup"
	BalanceTransactionReportingCategoryTopupReversal               BalanceTransactionReportingCategory = "topup_reversal"
	BalanceTransactionReportingCategoryTransfer                    BalanceTransactionReportingCategory = "transfer"
	BalanceTransactionReportingCategoryTransferReversal            BalanceTransactionReportingCategory = "transfer_reversal"
)

// BalanceTransactionSource describes the source of a balance Transaction.
// The Type should indicate which object is fleshed out.
// For more details see https://stripe.com/docs/api#retrieve_balance_transaction
type BalanceTransactionSource struct {
	ApplicationFee       *ApplicationFee              `json:"-"`
	Charge               *Charge                      `json:"-"`
	Dispute              *Dispute                     `json:"-"`
	ID                   string                       `json:"id"`
	IssuingAuthorization *IssuingAuthorization        `json:"-"`
	IssuingTransaction   *IssuingAuthorization        `json:"-"`
	Payout               *Payout                      `json:"-"`
	Refund               *Refund                      `json:"-"`
	Reversal             *Reversal                    `json:"-"`
	Transfer             *Transfer                    `json:"-"`
	Type                 BalanceTransactionSourceType `json:"object"`
}

// BalanceTransactionParams is the set of parameters that can be used when retrieving a transaction.
// For more details see https://stripe.com/docs/api#retrieve_balance_transaction.
type BalanceTransactionParams struct {
	Params `form:"*"`
}

// BalanceTransactionListParams is the set of parameters that can be used when listing balance transactions.
// For more details see https://stripe.com/docs/api/#balance_history.
type BalanceTransactionListParams struct {
	ListParams       `form:"*"`
	AvailableOn      *int64            `form:"available_on"`
	AvailableOnRange *RangeQueryParams `form:"available_on"`
	Created          *int64            `form:"created"`
	CreatedRange     *RangeQueryParams `form:"created"`
	Currency         *string           `form:"currency"`
	Payout           *string           `form:"payout"`
	Source           *string           `form:"source"`
	Type             *string           `form:"type"`
}

// BalanceTransaction is the resource representing the balance transaction.
// For more details see https://stripe.com/docs/api/#balance.
type BalanceTransaction struct {
	APIResource
	Amount            int64                               `json:"amount"`
	AvailableOn       int64                               `json:"available_on"`
	Created           int64                               `json:"created"`
	Currency          Currency                            `json:"currency"`
	Description       string                              `json:"description"`
	ExchangeRate      float64                             `json:"exchange_rate"`
	ID                string                              `json:"id"`
	Fee               int64                               `json:"fee"`
	FeeDetails        []*BalanceTransactionFee            `json:"fee_details"`
	Net               int64                               `json:"net"`
	ReportingCategory BalanceTransactionReportingCategory `json:"reporting_category"`
	Source            *BalanceTransactionSource           `json:"source"`
	Status            BalanceTransactionStatus            `json:"status"`
	Type              BalanceTransactionType              `json:"type"`
}

// BalanceTransactionList is a list of transactions as returned from a list endpoint.
type BalanceTransactionList struct {
	APIResource
	ListMeta
	Data []*BalanceTransaction `json:"data"`
}

// BalanceTransactionFee is a structure that breaks down the fees in a transaction.
type BalanceTransactionFee struct {
	Amount      int64    `json:"amount"`
	Application string   `json:"application"`
	Currency    Currency `json:"currency"`
	Description string   `json:"description"`
	Type        string   `json:"type"`
}

// UnmarshalJSON handles deserialization of a Transaction.
// This custom unmarshaling is needed because the resulting
// property may be an id or the full struct if it was expanded.
func (t *BalanceTransaction) UnmarshalJSON(data []byte) error {
	if id, ok := ParseID(data); ok {
		t.ID = id
		return nil
	}

	type balanceTransaction BalanceTransaction
	var v balanceTransaction
	if err := json.Unmarshal(data, &v); err != nil {
		return err
	}

	*t = BalanceTransaction(v)
	return nil
}

// UnmarshalJSON handles deserialization of a BalanceTransactionSource.
// This custom unmarshaling is needed because the specific
// type of transaction source it refers to is specified in the JSON
func (s *BalanceTransactionSource) UnmarshalJSON(data []byte) error {
	if id, ok := ParseID(data); ok {
		s.ID = id
		return nil
	}

	type balanceTransactionSource BalanceTransactionSource
	var v balanceTransactionSource
	if err := json.Unmarshal(data, &v); err != nil {
		return err
	}

	var err error
	*s = BalanceTransactionSource(v)

	switch s.Type {
	case BalanceTransactionSourceTypeApplicationFee:
		err = json.Unmarshal(data, &s.ApplicationFee)
	case BalanceTransactionSourceTypeCharge:
		err = json.Unmarshal(data, &s.Charge)
	case BalanceTransactionSourceTypeDispute:
		err = json.Unmarshal(data, &s.Dispute)
	case BalanceTransactionSourceTypeIssuingAuthorization:
		err = json.Unmarshal(data, &s.IssuingAuthorization)
	case BalanceTransactionSourceTypeIssuingTransaction:
		err = json.Unmarshal(data, &s.IssuingTransaction)
	case BalanceTransactionSourceTypePayout:
		err = json.Unmarshal(data, &s.Payout)
	case BalanceTransactionSourceTypeRefund:
		err = json.Unmarshal(data, &s.Refund)
	case BalanceTransactionSourceTypeReversal:
		err = json.Unmarshal(data, &s.Reversal)
	case BalanceTransactionSourceTypeTransfer:
		err = json.Unmarshal(data, &s.Transfer)
	}

	return err
}
<commit_msg>Add support for BalanceTransactionTypeContribution` on `BalanceTransaction`<commit_after>package stripe

import "encoding/json"

// List of values that BalanceTransactionStatus can take.
const (
	BalanceTransactionStatusAvailable BalanceTransactionStatus = "available"
	BalanceTransactionStatusPending   BalanceTransactionStatus = "pending"
)

// BalanceTransactionType is the list of allowed values for the balance transaction's type.
type BalanceTransactionType string

// List of values that BalanceTransactionType can take.
const (
	BalanceTransactionTypeAdjustment                      BalanceTransactionType = "adjustment"
	BalanceTransactionTypeAnticipationRepayment           BalanceTransactionType = "anticipation_repayment"
	BalanceTransactionTypeApplicationFee                  BalanceTransactionType = "application_fee"
	BalanceTransactionTypeApplicationFeeRefund            BalanceTransactionType = "application_fee_refund"
	BalanceTransactionTypeCharge                          BalanceTransactionType = "charge"
	BalanceTransactionTypeContribution                    BalanceTransactionType = "contribution"
	BalanceTransactionTypeIssuingAuthorizationHold        BalanceTransactionType = "issuing_authorization_hold"
	BalanceTransactionTypeIssuingAuthorizationRelease     BalanceTransactionType = "issuing_authorization_release"
	BalanceTransactionTypeIssuingAuthorizationDispute     BalanceTransactionType = "issuing_dispute"
	BalanceTransactionTypeIssuingAuthorizationTransaction BalanceTransactionType = "issuing_transaction"
	BalanceTransactionTypePayment                         BalanceTransactionType = "payment"
	BalanceTransactionTypePaymentFailureRefund            BalanceTransactionType = "payment_failure_refund"
	BalanceTransactionTypePaymentRefund                   BalanceTransactionType = "payment_refund"
	BalanceTransactionTypePayout                          BalanceTransactionType = "payout"
	BalanceTransactionTypePayoutCancel                    BalanceTransactionType = "payout_cancel"
	BalanceTransactionTypePayoutFailure                   BalanceTransactionType = "payout_failure"
	BalanceTransactionTypeRefund                          BalanceTransactionType = "refund"
	BalanceTransactionTypeStripeFee                       BalanceTransactionType = "stripe_fee"
	BalanceTransactionTypeTransfer                        BalanceTransactionType = "transfer"
	BalanceTransactionTypeTransferRefund                  BalanceTransactionType = "transfer_refund"
)

// BalanceTransactionSourceType consts represent valid balance transaction sources.
type BalanceTransactionSourceType string

// List of values that BalanceTransactionSourceType can take.
const (
	BalanceTransactionSourceTypeApplicationFee       BalanceTransactionSourceType = "application_fee"
	BalanceTransactionSourceTypeCharge               BalanceTransactionSourceType = "charge"
	BalanceTransactionSourceTypeDispute              BalanceTransactionSourceType = "dispute"
	BalanceTransactionSourceTypeIssuingAuthorization BalanceTransactionSourceType = "issuing.authorization"
	BalanceTransactionSourceTypeIssuingTransaction   BalanceTransactionSourceType = "issuing.transaction"
	BalanceTransactionSourceTypePayout               BalanceTransactionSourceType = "payout"
	BalanceTransactionSourceTypeRefund               BalanceTransactionSourceType = "refund"
	BalanceTransactionSourceTypeReversal             BalanceTransactionSourceType = "reversal"
	BalanceTransactionSourceTypeTransfer             BalanceTransactionSourceType = "transfer"
)

// BalanceTransactionReportingCategory represents reporting categories for balance transactions.
type BalanceTransactionReportingCategory string

// List of values that BalanceTransactionReportingCategory can take.
const (
	BalanceTransactionReportingCategoryAdvance                     BalanceTransactionReportingCategory = "advance"
	BalanceTransactionReportingCategoryAdvanceFunding              BalanceTransactionReportingCategory = "advance_funding"
	BalanceTransactionReportingCategoryCharge                      BalanceTransactionReportingCategory = "charge"
	BalanceTransactionReportingCategoryChargeFailure               BalanceTransactionReportingCategory = "charge_failure"
	BalanceTransactionReportingCategoryConnectCollectionTransfer   BalanceTransactionReportingCategory = "connect_collection_transfer"
	BalanceTransactionReportingCategoryConnectReservedFunds        BalanceTransactionReportingCategory = "connect_reserved_funds"
	BalanceTransactionReportingCategoryDispute                     BalanceTransactionReportingCategory = "dispute"
	BalanceTransactionReportingCategoryDisputeReversal             BalanceTransactionReportingCategory = "dispute_reversal"
	BalanceTransactionReportingCategoryFee                         BalanceTransactionReportingCategory = "fee"
	BalanceTransactionReportingCategoryIssuingAuthorizationHold    BalanceTransactionReportingCategory = "issuing_authorization_hold"
	BalanceTransactionReportingCategoryIssuingAuthorizationRelease BalanceTransactionReportingCategory = "issuing_authorization_release"
	BalanceTransactionReportingCategoryIssuingTransaction          BalanceTransactionReportingCategory = "issuing_transaction"
	BalanceTransactionReportingCategoryOtherAdjustment             BalanceTransactionReportingCategory = "other_adjustment"
	BalanceTransactionReportingCategoryPartialCaptureReversal      BalanceTransactionReportingCategory = "partial_capture_reversal"
	BalanceTransactionReportingCategoryPayout                      BalanceTransactionReportingCategory = "payout"
	BalanceTransactionReportingCategoryPayoutReversal              BalanceTransactionReportingCategory = "payout_reversal"
	BalanceTransactionReportingCategoryPlatformEarning             BalanceTransactionReportingCategory = "platform_earning"
	BalanceTransactionReportingCategoryPlatformEarningRefund       BalanceTransactionReportingCategory = "platform_earning_refund"
	BalanceTransactionReportingCategoryRefund                      BalanceTransactionReportingCategory = "refund"
	BalanceTransactionReportingCategoryRefundFailure               BalanceTransactionReportingCategory = "refund_failure"
	BalanceTransactionReportingCategoryRiskReservedFunds           BalanceTransactionReportingCategory = "risk_reserved_funds"
	BalanceTransactionReportingCategoryTax                         BalanceTransactionReportingCategory = "tax"
	BalanceTransactionReportingCategoryTopup                       BalanceTransactionReportingCategory = "topup"
	BalanceTransactionReportingCategoryTopupReversal               BalanceTransactionReportingCategory = "topup_reversal"
	BalanceTransactionReportingCategoryTransfer                    BalanceTransactionReportingCategory = "transfer"
	BalanceTransactionReportingCategoryTransferReversal            BalanceTransactionReportingCategory = "transfer_reversal"
)

// BalanceTransactionSource describes the source of a balance Transaction.
// The Type should indicate which object is fleshed out.
// For more details see https://stripe.com/docs/api#retrieve_balance_transaction
type BalanceTransactionSource struct {
	ApplicationFee       *ApplicationFee              `json:"-"`
	Charge               *Charge                      `json:"-"`
	Dispute              *Dispute                     `json:"-"`
	ID                   string                       `json:"id"`
	IssuingAuthorization *IssuingAuthorization        `json:"-"`
	IssuingTransaction   *IssuingAuthorization        `json:"-"`
	Payout               *Payout                      `json:"-"`
	Refund               *Refund                      `json:"-"`
	Reversal             *Reversal                    `json:"-"`
	Transfer             *Transfer                    `json:"-"`
	Type                 BalanceTransactionSourceType `json:"object"`
}

// BalanceTransactionParams is the set of parameters that can be used when retrieving a transaction.
// For more details see https://stripe.com/docs/api#retrieve_balance_transaction.
type BalanceTransactionParams struct {
	Params `form:"*"`
}

// BalanceTransactionListParams is the set of parameters that can be used when listing balance transactions.
// For more details see https://stripe.com/docs/api/#balance_history.
type BalanceTransactionListParams struct {
	ListParams       `form:"*"`
	AvailableOn      *int64            `form:"available_on"`
	AvailableOnRange *RangeQueryParams `form:"available_on"`
	Created          *int64            `form:"created"`
	CreatedRange     *RangeQueryParams `form:"created"`
	Currency         *string           `form:"currency"`
	Payout           *string           `form:"payout"`
	Source           *string           `form:"source"`
	Type             *string           `form:"type"`
}

// BalanceTransaction is the resource representing the balance transaction.
// For more details see https://stripe.com/docs/api/#balance.
type BalanceTransaction struct {
	APIResource
	Amount            int64                               `json:"amount"`
	AvailableOn       int64                               `json:"available_on"`
	Created           int64                               `json:"created"`
	Currency          Currency                            `json:"currency"`
	Description       string                              `json:"description"`
	ExchangeRate      float64                             `json:"exchange_rate"`
	ID                string                              `json:"id"`
	Fee               int64                               `json:"fee"`
	FeeDetails        []*BalanceTransactionFee            `json:"fee_details"`
	Net               int64                               `json:"net"`
	ReportingCategory BalanceTransactionReportingCategory `json:"reporting_category"`
	Source            *BalanceTransactionSource           `json:"source"`
	Status            BalanceTransactionStatus            `json:"status"`
	Type              BalanceTransactionType              `json:"type"`
}

// BalanceTransactionList is a list of transactions as returned from a list endpoint.
type BalanceTransactionList struct {
	APIResource
	ListMeta
	Data []*BalanceTransaction `json:"data"`
}

// BalanceTransactionFee is a structure that breaks down the fees in a transaction.
type BalanceTransactionFee struct {
	Amount      int64    `json:"amount"`
	Application string   `json:"application"`
	Currency    Currency `json:"currency"`
	Description string   `json:"description"`
	Type        string   `json:"type"`
}

// UnmarshalJSON handles deserialization of a Transaction.
// This custom unmarshaling is needed because the resulting
// property may be an id or the full struct if it was expanded.
func (t *BalanceTransaction) UnmarshalJSON(data []byte) error {
	if id, ok := ParseID(data); ok {
		t.ID = id
		return nil
	}

	type balanceTransaction BalanceTransaction
	var v balanceTransaction
	if err := json.Unmarshal(data, &v); err != nil {
		return err
	}

	*t = BalanceTransaction(v)
	return nil
}

// UnmarshalJSON handles deserialization of a BalanceTransactionSource.
// This custom unmarshaling is needed because the specific
// type of transaction source it refers to is specified in the JSON
func (s *BalanceTransactionSource) UnmarshalJSON(data []byte) error {
	if id, ok := ParseID(data); ok {
		s.ID = id
		return nil
	}

	type balanceTransactionSource BalanceTransactionSource
	var v balanceTransactionSource
	if err := json.Unmarshal(data, &v); err != nil {
		return err
	}

	var err error
	*s = BalanceTransactionSource(v)

	switch s.Type {
	case BalanceTransactionSourceTypeApplicationFee:
		err = json.Unmarshal(data, &s.ApplicationFee)
	case BalanceTransactionSourceTypeCharge:
		err = json.Unmarshal(data, &s.Charge)
	case BalanceTransactionSourceTypeDispute:
		err = json.Unmarshal(data, &s.Dispute)
	case BalanceTransactionSourceTypeIssuingAuthorization:
		err = json.Unmarshal(data, &s.IssuingAuthorization)
	case BalanceTransactionSourceTypeIssuingTransaction:
		err = json.Unmarshal(data, &s.IssuingTransaction)
	case BalanceTransactionSourceTypePayout:
		err = json.Unmarshal(data, &s.Payout)
	case BalanceTransactionSourceTypeRefund:
		err = json.Unmarshal(data, &s.Refund)
	case BalanceTransactionSourceTypeReversal:
		err = json.Unmarshal(data, &s.Reversal)
	case BalanceTransactionSourceTypeTransfer:
		err = json.Unmarshal(data, &s.Transfer)
	}

	return err
}
<|endoftext|>


<commit_before>package controllers

import (
	"github.com/astaxie/beego"
)

var (
	globaleAppName        string = "/" + beego.AppConfig.String("appname")
	defaultUserName       string = beego.AppConfig.String("username")
	defaultPassword       string = beego.AppConfig.String("password")
	loginAction           string = globaleAppName + "/login"
	indexAction           string = globaleAppName + "/index"
	microWebsocket        string = "/microboshwebsocket"
	cloudfoundryWebsocket string = "/cloudfoundrywebsocket"
	boshWebsocket         string = "/boshwebsocket"

	workSpace    string = beego.AppConfig.String("workSpace")
	workDir      string = workSpace + "/" + beego.AppConfig.String("workDir")
	microManiest string = "microbosh/micro_bosh.yml"
	microPath    string = workDir + "/" + microManiest

	cloudFoundryManiest string = "cloudfoundry/cloudfoundry.yml"
	cloudFoundryPath    string = workDir + "/" + cloudFoundryManiest
	cloudFoundryName           = beego.AppConfig.String("paasName")
	cloudFoundryVersion        = beego.AppConfig.String("paasVersion")
	cloudFoundryRelease string = workSpace + "/" + beego.AppConfig.String("paasRelease")

	iaasVersion    = beego.AppConfig.String("iaas-version")
	defaultVersion = "CCI-IaaS3.0"

	stemcellName           = beego.AppConfig.String("stemcellName")
	stemcellVersion        = beego.AppConfig.String("stemcellVersion")
	stemcellRelease string = workSpace + "/" + beego.AppConfig.String("stemcellRelease")
)
<commit_msg>delete controllers/actions<commit_after><|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package controllers

import (
	"github.com/ernestio/api-gateway/controllers/builds"
	"github.com/ernestio/api-gateway/controllers/envs"
	h "github.com/ernestio/api-gateway/helpers"
	"github.com/labstack/echo"
)

// ActionHandler : handles different actions that can be triggered on an env
func ActionHandler(c echo.Context) error {
	au := AuthenticatedUser(c)
	st, b := h.IsAuthorized(&au, "envs/create")
	if st != 200 {
		return h.Respond(c, st, b)
	}

	action, err := mapAction(c)
	if err != nil {
		return h.Respond(c, 400, []byte(err.Error()))
	}

	switch action.Type {
	case "import":
		st, b = builds.Import(au, envName(c), action)
	case "reset":
		st, b = envs.Reset(au, envName(c), action)
	default:
		return h.Respond(c, 400, []byte("unsupported action"))
	}

	return h.Respond(c, st, b)
}
<commit_msg>updated action controller<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package controllers

import (
	"github.com/ernestio/api-gateway/controllers/builds"
	"github.com/ernestio/api-gateway/controllers/envs"
	h "github.com/ernestio/api-gateway/helpers"
	"github.com/labstack/echo"
)

// ActionHandler : handles different actions that can be triggered on an env
func ActionHandler(c echo.Context) error {
	au := AuthenticatedUser(c)
	st, b := h.IsAuthorized(&au, "envs/create")
	if st != 200 {
		return h.Respond(c, st, b)
	}

	action, err := mapAction(c)
	if err != nil {
		return h.Respond(c, 400, []byte(err.Error()))
	}

	switch action.Type {
	case "import":
		st, b = builds.Import(au, envName(c), action)
	case "reset":
		st, b = envs.Reset(au, envName(c), action)
	case "sync":
		st, b = envs.Sync(au, envName(c), action)
	default:
		return h.Respond(c, 400, []byte("unsupported action"))
	}

	return h.Respond(c, st, b)
}
<|endoftext|>


<commit_before>package golexer

import (
	"errors"
)

type Lexer struct {
	matchers []matcherMeta

	comm chan tokenAndError

	running bool
}

type matcherMeta struct {
	m      TokenMatcher
	ignore bool
}

type tokenAndError struct {
	tk  *Token
	err error
}

var eofToken = tokenAndError{NewToken(nil, nil, "EOF", ""), nil}

func (self *Lexer) VisitMatcher(callback func(TokenMatcher) bool) {

	for _, m := range self.matchers {
		if !callback(m.m) {
			return
		}
	}
}

func (self *Lexer) MatcherString(id int) string {
	m := self.MatcherByID(id)
	if m != nil {
		return m.String()
	}

	return ""
}

func (self *Lexer) MatcherByID(id int) TokenMatcher {
	for _, m := range self.matchers {
		if m.m.ID() == id {
			return m.m
		}
	}

	return nil
}

// 添加一个匹配器，如果结果匹配，返回token
func (self *Lexer) AddMatcher(m TokenMatcher) {
	self.matchers = append(self.matchers, matcherMeta{
		m:      m,
		ignore: false,
	})
}

// 添加一个匹配器，如果结果匹配，直接忽略匹配内容
func (self *Lexer) AddIgnoreMatcher(m TokenMatcher) {
	self.matchers = append(self.matchers, matcherMeta{
		m:      m,
		ignore: true,
	})
}

func (self *Lexer) Start(src string) {

	if self.comm != nil {
		close(self.comm)
	}

	self.comm = make(chan tokenAndError)

	self.running = true

	go self.tokenWorker(src)
}

func (self *Lexer) Read() (*Token, error) {

	if !self.running {
		return eofToken.tk, nil
	}

	if self.comm == nil {
		return eofToken.tk, errors.New("call 'Start' first")
	}

	te := <-self.comm

	if te.err != nil || te.tk.MatcherID() == 0 {
		self.running = false
	}

	return te.tk, te.err
}

func (self *Lexer) tokenWorker(src string) {

	tz := NewTokenizer(src, self)

	if len(self.matchers) > 0 {

		for !tz.EOF() {

			for _, mm := range self.matchers {

				token, err := mm.m.Match(tz)

				if err != nil {
					self.comm <- tokenAndError{NewToken(nil, tz, err.Error(), ""), err}
					return
				}

				if token == nil {
					continue
				}

				if mm.ignore {
					break
				}

				self.comm <- tokenAndError{token, nil}

				// 重新从matcher开始检查
				break

			}
		}
	}

	self.comm <- eofToken
}

func NewLexer() *Lexer {

	return &Lexer{}

}
<commit_msg>去掉goroutine取token设计<commit_after>package golexer

type Lexer struct {
	matchers []matcherMeta

	running bool

	tz *Tokenizer
}

type matcherMeta struct {
	m      TokenMatcher
	ignore bool
}

var eofToken = NewToken(nil, nil, "EOF", "")

func (self *Lexer) VisitMatcher(callback func(TokenMatcher) bool) {

	for _, m := range self.matchers {
		if !callback(m.m) {
			return
		}
	}
}

func (self *Lexer) MatcherString(id int) string {
	m := self.MatcherByID(id)
	if m != nil {
		return m.String()
	}

	return ""
}

func (self *Lexer) MatcherByID(id int) TokenMatcher {
	for _, m := range self.matchers {
		if m.m.ID() == id {
			return m.m
		}
	}

	return nil
}

// 添加一个匹配器，如果结果匹配，返回token
func (self *Lexer) AddMatcher(m TokenMatcher) {
	self.matchers = append(self.matchers, matcherMeta{
		m:      m,
		ignore: false,
	})
}

// 添加一个匹配器，如果结果匹配，直接忽略匹配内容
func (self *Lexer) AddIgnoreMatcher(m TokenMatcher) {
	self.matchers = append(self.matchers, matcherMeta{
		m:      m,
		ignore: true,
	})
}

func (self *Lexer) Start(src string) {

	self.running = true

	self.tz = NewTokenizer(src, self)
}

func (self *Lexer) Read() (*Token, error) {

	if !self.running {
		return eofToken, nil
	}

	tk, err := self.readToken()

	if err != nil || tk.MatcherID() == 0 {
		self.running = false
	}

	return tk, err
}

func (self *Lexer) readToken() (*Token, error) {

	if len(self.matchers) == 0 {
		return eofToken, nil
	}

	for !self.tz.EOF() {

		for _, mm := range self.matchers {

			token, err := mm.m.Match(self.tz)

			if err != nil {
				return NewToken(nil, self.tz, err.Error(), ""), err
			}

			if token == nil {
				continue
			}

			if mm.ignore {
				break
			}

			return token, nil
		}
	}

	return eofToken, nil
}

func NewLexer() *Lexer {

	return &Lexer{}

}
<|endoftext|>