<commit_before>// Copyright 2011 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.

// +build !appengine

package main

import (
	"flag"
	"fmt"
	"go/build"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	"code.google.com/p/go.tools/playground/socket"

	// Imports so that go build/install automatically installs them.
	_ "code.google.com/p/go-tour/pic"
	_ "code.google.com/p/go-tour/tree"
	_ "code.google.com/p/go-tour/wc"
)

const (
	basePkg    = "code.google.com/p/go-tour/"
	socketPath = "/socket"
)

var (
	httpListen  = flag.String("http", "127.0.0.1:3999", "host:port to listen on")
	openBrowser = flag.Bool("openbrowser", true, "open browser automatically")
)

var (
	// GOPATH containing the tour packages
	gopath = os.Getenv("GOPATH")

	httpAddr string
)

// isRoot reports whether path is the root directory of the tour tree.
// To be the root, it must have content and template subdirectories.
func isRoot(path string) bool {
	_, err := os.Stat(filepath.Join(path, "content", "welcome.article"))
	if err == nil {
		_, err = os.Stat(filepath.Join(path, "template", "index.tmpl"))
	}
	return err == nil
}

func findRoot() (string, error) {
	ctx := build.Default
	p, err := ctx.Import(basePkg, "", build.FindOnly)
	if err == nil && isRoot(p.Dir) {
		return p.Dir, nil
	}
	tourRoot := filepath.Join(runtime.GOROOT(), "misc", "tour")
	ctx.GOPATH = tourRoot
	p, err = ctx.Import(basePkg, "", build.FindOnly)
	if err == nil && isRoot(tourRoot) {
		gopath = tourRoot
		return tourRoot, nil
	}
	return "", fmt.Errorf("could not find go-tour content; check $GOROOT and $GOPATH")
}

func main() {
	flag.Parse()

	// find and serve the go tour files
	root, err := findRoot()
	if err != nil {
		log.Fatalf("Couldn't find tour files: %v", err)
	}

	log.Println("Serving content from", root)

	host, port, err := net.SplitHostPort(*httpListen)
	if err != nil {
		log.Fatal(err)
	}
	if host == "" {
		host = "localhost"
	}
	if host != "127.0.0.1" && host != "localhost" {
		log.Print(localhostWarning)
	}
	httpAddr = host + ":" + port

	if err := initTour(root, "SocketTransport"); err != nil {
		log.Fatal(err)
	}

	http.HandleFunc("/", rootHandler)
	http.HandleFunc("/lesson/", lessonHandler)
	http.Handle(socketPath, socket.Handler)

	// Keep these static file handlers in sync with ../app.yaml.
	static := http.FileServer(http.Dir(root))
	http.Handle("/content/img/", static)
	http.Handle("/static/", static)
	imgDir := filepath.Join(root, "static", "img")
	http.Handle("/favicon.ico", http.FileServer(http.Dir(imgDir)))

	go func() {
		url := "http://" + httpAddr
		if waitServer(url) && *openBrowser && startBrowser(url) {
			log.Printf("A browser window should open. If not, please visit %s", url)
		} else {
			log.Printf("Please open your web browser and visit %s", url)
		}
	}()
	log.Fatal(http.ListenAndServe(httpAddr, nil))
}

// rootHandler returns a handler for all the requests except the ones for lessons.
func rootHandler(w http.ResponseWriter, r *http.Request) {
	if err := renderUI(w); err != nil {
		log.Println(err)
	}
}

// lessonHandler handler the HTTP requests for lessons.
func lessonHandler(w http.ResponseWriter, r *http.Request) {
	lesson := strings.TrimPrefix(r.URL.Path, "/lesson/")
	if err := writeLesson(lesson, w); err != nil {
		if err == lessonNotFound {
			http.NotFound(w, r)
		} else {
			log.Println(err)
		}
	}
}

const localhostWarning = `
WARNING!  WARNING!  WARNING!

I appear to be listening on an address that is not localhost.
Anyone with access to this address and port will have access
to this machine as the user running gotour.

If you don't understand this message, hit Control-C to terminate this process.

WARNING!  WARNING!  WARNING!
`

type response struct {
	Output string `json:"output"`
	Errors string `json:"compile_errors"`
}

func init() {
	socket.Environ = environ
}

// environ returns the original execution environment with GOPATH
// replaced (or added) with the value of the global var gopath.
func environ() (env []string) {
	for _, v := range os.Environ() {
		if !strings.HasPrefix(v, "GOPATH=") {
			env = append(env, v)
		}
	}
	env = append(env, "GOPATH="+gopath)
	return
}

// waitServer waits some time for the http Server to start
// serving url. The return value reports whether it starts.
func waitServer(url string) bool {
	tries := 20
	for tries > 0 {
		resp, err := http.Get(url)
		if err == nil {
			resp.Body.Close()
			return true
		}
		time.Sleep(100 * time.Millisecond)
		tries--
	}
	return false
}

// startBrowser tries to open the URL in a browser, and returns
// whether it succeed.
func startBrowser(url string) bool {
	// try to start the browser
	var args []string
	switch runtime.GOOS {
	case "darwin":
		args = []string{"open"}
	case "windows":
		args = []string{"cmd", "/c", "start"}
	default:
		args = []string{"xdg-open"}
	}
	cmd := exec.Command(args[0], append(args[1:], url)...)
	return cmd.Start() == nil
}

// prepContent for the local tour simply returns the content as-is.
func prepContent(r io.Reader) io.Reader { return r }

// socketAddr returns the WebSocket handler address.
func socketAddr() string { return "ws://" + httpAddr + socketPath }
<commit_msg>[x/tour] go-tour: use new socket interface<commit_after>// Copyright 2011 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.

// +build !appengine

package main

import (
	"flag"
	"fmt"
	"go/build"
	"io"
	"log"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	"code.google.com/p/go.tools/playground/socket"

	// Imports so that go build/install automatically installs them.
	_ "code.google.com/p/go-tour/pic"
	_ "code.google.com/p/go-tour/tree"
	_ "code.google.com/p/go-tour/wc"
)

const (
	basePkg    = "code.google.com/p/go-tour/"
	socketPath = "/socket"
)

var (
	httpListen  = flag.String("http", "127.0.0.1:3999", "host:port to listen on")
	openBrowser = flag.Bool("openbrowser", true, "open browser automatically")
)

var (
	// GOPATH containing the tour packages
	gopath = os.Getenv("GOPATH")

	httpAddr string
)

// isRoot reports whether path is the root directory of the tour tree.
// To be the root, it must have content and template subdirectories.
func isRoot(path string) bool {
	_, err := os.Stat(filepath.Join(path, "content", "welcome.article"))
	if err == nil {
		_, err = os.Stat(filepath.Join(path, "template", "index.tmpl"))
	}
	return err == nil
}

func findRoot() (string, error) {
	ctx := build.Default
	p, err := ctx.Import(basePkg, "", build.FindOnly)
	if err == nil && isRoot(p.Dir) {
		return p.Dir, nil
	}
	tourRoot := filepath.Join(runtime.GOROOT(), "misc", "tour")
	ctx.GOPATH = tourRoot
	p, err = ctx.Import(basePkg, "", build.FindOnly)
	if err == nil && isRoot(tourRoot) {
		gopath = tourRoot
		return tourRoot, nil
	}
	return "", fmt.Errorf("could not find go-tour content; check $GOROOT and $GOPATH")
}

func main() {
	flag.Parse()

	// find and serve the go tour files
	root, err := findRoot()
	if err != nil {
		log.Fatalf("Couldn't find tour files: %v", err)
	}

	log.Println("Serving content from", root)

	host, port, err := net.SplitHostPort(*httpListen)
	if err != nil {
		log.Fatal(err)
	}
	if host == "" {
		host = "localhost"
	}
	if host != "127.0.0.1" && host != "localhost" {
		log.Print(localhostWarning)
	}
	httpAddr = host + ":" + port

	if err := initTour(root, "SocketTransport"); err != nil {
		log.Fatal(err)
	}

	http.HandleFunc("/", rootHandler)
	http.HandleFunc("/lesson/", lessonHandler)

	origin := &url.URL{Scheme: "http", Host: host + ":" + port}
	http.Handle(socketPath, socket.NewHandler(origin))

	// Keep these static file handlers in sync with ../app.yaml.
	static := http.FileServer(http.Dir(root))
	http.Handle("/content/img/", static)
	http.Handle("/static/", static)
	imgDir := filepath.Join(root, "static", "img")
	http.Handle("/favicon.ico", http.FileServer(http.Dir(imgDir)))

	go func() {
		url := "http://" + httpAddr
		if waitServer(url) && *openBrowser && startBrowser(url) {
			log.Printf("A browser window should open. If not, please visit %s", url)
		} else {
			log.Printf("Please open your web browser and visit %s", url)
		}
	}()
	log.Fatal(http.ListenAndServe(httpAddr, nil))
}

// rootHandler returns a handler for all the requests except the ones for lessons.
func rootHandler(w http.ResponseWriter, r *http.Request) {
	if err := renderUI(w); err != nil {
		log.Println(err)
	}
}

// lessonHandler handler the HTTP requests for lessons.
func lessonHandler(w http.ResponseWriter, r *http.Request) {
	lesson := strings.TrimPrefix(r.URL.Path, "/lesson/")
	if err := writeLesson(lesson, w); err != nil {
		if err == lessonNotFound {
			http.NotFound(w, r)
		} else {
			log.Println(err)
		}
	}
}

const localhostWarning = `
WARNING!  WARNING!  WARNING!

I appear to be listening on an address that is not localhost.
Anyone with access to this address and port will have access
to this machine as the user running gotour.

If you don't understand this message, hit Control-C to terminate this process.

WARNING!  WARNING!  WARNING!
`

type response struct {
	Output string `json:"output"`
	Errors string `json:"compile_errors"`
}

func init() {
	socket.Environ = environ
}

// environ returns the original execution environment with GOPATH
// replaced (or added) with the value of the global var gopath.
func environ() (env []string) {
	for _, v := range os.Environ() {
		if !strings.HasPrefix(v, "GOPATH=") {
			env = append(env, v)
		}
	}
	env = append(env, "GOPATH="+gopath)
	return
}

// waitServer waits some time for the http Server to start
// serving url. The return value reports whether it starts.
func waitServer(url string) bool {
	tries := 20
	for tries > 0 {
		resp, err := http.Get(url)
		if err == nil {
			resp.Body.Close()
			return true
		}
		time.Sleep(100 * time.Millisecond)
		tries--
	}
	return false
}

// startBrowser tries to open the URL in a browser, and returns
// whether it succeed.
func startBrowser(url string) bool {
	// try to start the browser
	var args []string
	switch runtime.GOOS {
	case "darwin":
		args = []string{"open"}
	case "windows":
		args = []string{"cmd", "/c", "start"}
	default:
		args = []string{"xdg-open"}
	}
	cmd := exec.Command(args[0], append(args[1:], url)...)
	return cmd.Start() == nil
}

// prepContent for the local tour simply returns the content as-is.
func prepContent(r io.Reader) io.Reader { return r }

// socketAddr returns the WebSocket handler address.
func socketAddr() string { return "ws://" + httpAddr + socketPath }
<|endoftext|>


<commit_before>//Package soda provides HTTP GET tools for SODA (Socrata Open Data API) webservices, see http://dev.socrata.com/
package soda

import (
	"encoding/csv"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"sync"
	"time"
)

//GetRequest is a wrapper/container for SODA requests.
//This is NOT safe for use by multiple goroutines as Format, Filters and Query will be overwritten.
//Create a new GetRequest in each goroutine you use or use an OffsetGetRequest
type GetRequest struct {
	apptoken string
	endpoint string //endpoint without format (not .json etc at the end)
	Format   string //json, csv etc
	Filters  SimpleFilters
	Query    SoSQL
	Metadata metadata
}

//NewGetRequest creates a new GET request, the endpoint must be specified without the format.
//For example https://data.ct.gov/resource/hma6-9xbg
func NewGetRequest(endpoint, apptoken string) *GetRequest {
	return &GetRequest{
		apptoken: apptoken,
		endpoint: endpoint,
		Filters:  make(SimpleFilters),
		Metadata: newMetadata(endpoint),
	}
}

//Get executes the HTTP GET request
func (r *GetRequest) Get() (*http.Response, error) {
	//If offset is used we must specify an order
	if r.Query.Offset > 0 && len(r.Query.Order) == 0 {
		return nil, fmt.Errorf("Cannot use an offset without setting the order")
	}
	return get(r, r.URLValues().Encode())
}

//GetEndpoint returns the complete SODA URL with format
func (r *GetRequest) GetEndpoint() string {
	if r.Format == "" {
		r.Format = "json"
	}
	return fmt.Sprintf("%s.%s", r.endpoint, r.Format)
}

//URLValues returns the url.Values for the GetRequest
func (r *GetRequest) URLValues() url.Values {
	uv := make(url.Values)
	for key, val := range r.Filters.URLValues() {
		uv[key] = val
	}
	for key, val := range r.Query.URLValues() {
		uv[key] = val
	}
	return uv
}

//Count gets the total number of records in the dataset
//by executing a SODA request
func (r *GetRequest) Count() (uint, error) {

	oldformat := r.Format
	oldorder := r.Query.Order
	oldselect := r.Query.Select
	defer func() {
		r.Format = oldformat
		r.Query.Order = oldorder
		r.Query.Select = oldselect
	}()

	r.Format = "json"
	r.Query.Select = []string{"count(*)"}
	r.Query.ClearOrder()

	resp, err := r.Get()
	if err != nil {
		return 0, err
	}
	defer resp.Body.Close()

	count := []struct {
		Count string
	}{}
	err = json.NewDecoder(resp.Body).Decode(&count)
	if err != nil {
		return 0, err
	}
	if len(count) == 0 {
		return 0, fmt.Errorf("Empty count response")
	}
	icount, err := strconv.Atoi(count[0].Count)
	if err != nil {
		return 0, err
	}
	return uint(icount), nil
}

//Fields returns all the fields present in the dataset (ignores select fields).
//Spaces in fieldnames are replaced by underscores.
func (r *GetRequest) Fields() ([]string, error) {

	oldformat := r.Format
	oldorder := r.Query.Order
	oldlimit := r.Query.Limit
	oldselect := r.Query.Select
	defer func() {
		r.Format = oldformat
		r.Query.Select = oldselect
		r.Query.Order = oldorder
		r.Query.Limit = oldlimit
	}()

	fields := []string{}

	r.Format = "csv"
	r.Query.Select = []string{}
	r.Query.Limit = 0
	r.Query.ClearOrder()

	resp, err := r.Get()
	if err != nil {
		return fields, err
	}
	defer resp.Body.Close()

	csvreader := csv.NewReader(resp.Body)
	record, err := csvreader.Read()
	if err != nil && err != io.EOF {
		return fields, err
	}
	for _, field := range record {
		fields = append(fields, strings.Replace(field, " ", "_", -1))
	}
	return fields, nil
}

//Modified returns when the dataset was last updated
func (r *GetRequest) Modified() (time.Time, error) {

	oldformat := r.Format
	oldorder := r.Query.Order
	oldlimit := r.Query.Limit
	oldselect := r.Query.Select
	defer func() {
		r.Format = oldformat
		r.Query.Select = oldselect
		r.Query.Order = oldorder
		r.Query.Limit = oldlimit
	}()

	r.Format = "json"
	r.Query.Select = []string{}
	r.Query.Limit = 0
	r.Query.ClearOrder()

	resp, err := r.Get()
	if err != nil {
		return time.Time{}, err
	}
	defer resp.Body.Close()

	lms := resp.Header.Get("X-Soda2-Truth-Last-Modified")
	if lms == "" {
		lms = resp.Header.Get("Last-Modified")
	}
	if lms == "" {
		return time.Time{}, fmt.Errorf("Cannot get last modified date, field not present in HTTP header")
	}

	return time.Parse(time.RFC1123, lms)
}

//SimpleFilters is the easiest way to filter columns for equality.
//Add the column to filter on a map key and the filter value as map value.
//If you include multiple filters, the filters will be combined using a boolean AND.
//See http://dev.socrata.com/docs/filtering.html
type SimpleFilters map[string]string

//URLValues returns the url.Values for the SimpleFilters
func (sf SimpleFilters) URLValues() url.Values {
	uv := make(url.Values)
	for key, val := range sf {
		uv.Add(key, val)
	}
	return uv
}

//SoSQL implements the Socrata Query Language and is used to build more complex queries.
//See http://dev.socrata.com/docs/queries.html
type SoSQL struct {
	Select []string //The set of columns to be returned. Default: All columns, equivalent to $select=*
	Where  string   //Filters the rows to be returned. Default: No filter, and returning a max of $limit values
	Order  []struct {
		Column string //Column name
		Desc   bool   //Descending. Default: false = Ascending
	} //Specifies the order of results. Default: Unspecified order, but it will be consistent across paging
	Group  string //Column to group results on, similar to SQL Grouping. Default: No grouping
	Limit  uint   //Maximum number of results to return. Default: 1000 (with a maximum of 50,000)
	Offset uint   //Offset count into the results to start at, used for paging. Default: 0
	Q      string //Performs a full text search for a value. Default: No search

}

//AddOrder can be called for each field you want to sort the result on.
//If parameter descending is true, the column will be sorted descending, or ascending if false.
func (sq *SoSQL) AddOrder(column string, descending bool) {
	sq.Order = append(sq.Order, struct {
		Column string
		Desc   bool
	}{column, descending})
}

//ClearOrder removes all order fields
func (sq *SoSQL) ClearOrder() {
	sq.Order = []struct {
		Column string
		Desc   bool
	}{}
}

//URLValues returns the url.Values for the SoSQL query
func (sq *SoSQL) URLValues() url.Values {
	uv := make(url.Values)
	if len(sq.Select) > 0 {
		uv.Add("$select", strings.Join(sq.Select, ","))
	}
	if len(sq.Where) > 0 {
		uv.Add("$where", sq.Where)
	}
	if len(sq.Order) > 0 {
		order := []string{}
		for _, o := range sq.Order {
			if o.Desc {
				order = append(order, o.Column+" DESC")
			} else {
				order = append(order, o.Column+" ASC")
			}
		}
		uv.Add("$order", strings.Join(order, ","))
	}
	if len(sq.Q) > 0 {
		uv.Add("$q", sq.Q)
	}
	if len(sq.Group) > 0 {
		uv.Add("$group", sq.Group)
	}
	if sq.Limit > 0 {
		uv.Add("$limit", fmt.Sprintf("%d", sq.Limit))
	}
	if sq.Offset > 0 {
		uv.Add("$offset", fmt.Sprintf("%d", sq.Offset))
	}
	return uv
}

//OffsetGetRequest is a request getter that gets all the records using the filters and limits from gr and
//is safe to use by multiple goroutines, use Next(number) to get the next number of records.
//A sync.WaitGroup is embedded for easy concurrency.
type OffsetGetRequest struct {
	gr     *GetRequest
	m      sync.Mutex
	offset uint
	count  uint
	sync.WaitGroup
}

//ErrDone is returned by OffsetGetRequest.Next when done
var ErrDone = fmt.Errorf("Done")

//Next gets the next number of records
func (o *OffsetGetRequest) Next(number uint) (*http.Response, error) {
	o.m.Lock() //lock to protect offset
	if o.IsDone() {
		o.m.Unlock()
		return nil, ErrDone
	}
	if len(o.gr.Query.Order) == 0 { //If offset is used we must specify an order
		return nil, fmt.Errorf("Cannot use an offset without setting the order")
	}
	if o.offset+number > o.count {
		number = o.count - o.offset
	}
	o.gr.Query.Offset = o.offset
	o.gr.Query.Limit = number
	rawquery := o.gr.URLValues().Encode()
	o.offset += number
	o.m.Unlock() //unlock before the request is done
	return get(o.gr, rawquery)
}

//Count returns the number of records from memory
func (o *OffsetGetRequest) Count() uint {
	return o.count
}

//IsDone returns if we have gotten all records
func (o *OffsetGetRequest) IsDone() bool {
	return o.offset >= o.count
}

//NewOffsetGetRequest creates a new OffsetGetRequest from gr
//and does a count request to determine the number of records to get
func NewOffsetGetRequest(gr *GetRequest) (*OffsetGetRequest, error) {
	count, err := gr.Count()
	if err != nil {
		return nil, err
	}
	return &OffsetGetRequest{gr: gr, offset: 0, count: count}, nil
}

//get is the function that executes the HTTP request
func get(r *GetRequest, rawquery string) (*http.Response, error) {

	req, err := http.NewRequest("GET", r.GetEndpoint(), nil)
	if err != nil {
		return nil, err
	}
	req.URL.RawQuery = rawquery
	req.Header.Set("X-App-Token", r.apptoken)

	//Execute
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode >= 400 {
		errormessage, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, err
		}
		return nil, fmt.Errorf("SODA error %d:\nURL: GET %s\nResponse: %s", resp.StatusCode, req.URL.String(), errormessage)
	}

	return resp, nil
}
<commit_msg>Add field for custom http client<commit_after>//Package soda provides HTTP GET tools for SODA (Socrata Open Data API) webservices, see http://dev.socrata.com/
package soda

import (
	"encoding/csv"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"sync"
	"time"
)

//GetRequest is a wrapper/container for SODA requests.
//This is NOT safe for use by multiple goroutines as Format, Filters and Query will be overwritten.
//Create a new GetRequest in each goroutine you use or use an OffsetGetRequest
type GetRequest struct {
	apptoken   string
	endpoint   string //endpoint without format (not .json etc at the end)
	Format     string //json, csv etc
	Filters    SimpleFilters
	Query      SoSQL
	Metadata   metadata
	HttpClient *http.Client //For clients who need a custom HTTP client
}

//NewGetRequest creates a new GET request, the endpoint must be specified without the format.
//For example https://data.ct.gov/resource/hma6-9xbg
func NewGetRequest(endpoint, apptoken string) *GetRequest {
	return &GetRequest{
		apptoken: apptoken,
		endpoint: endpoint,
		Filters:  make(SimpleFilters),
		Metadata: newMetadata(endpoint),
	}
}

//Get executes the HTTP GET request
func (r *GetRequest) Get() (*http.Response, error) {
	//If offset is used we must specify an order
	if r.Query.Offset > 0 && len(r.Query.Order) == 0 {
		return nil, fmt.Errorf("Cannot use an offset without setting the order")
	}
	return get(r, r.URLValues().Encode())
}

//GetEndpoint returns the complete SODA URL with format
func (r *GetRequest) GetEndpoint() string {
	if r.Format == "" {
		r.Format = "json"
	}
	return fmt.Sprintf("%s.%s", r.endpoint, r.Format)
}

//URLValues returns the url.Values for the GetRequest
func (r *GetRequest) URLValues() url.Values {
	uv := make(url.Values)
	for key, val := range r.Filters.URLValues() {
		uv[key] = val
	}
	for key, val := range r.Query.URLValues() {
		uv[key] = val
	}
	return uv
}

//Count gets the total number of records in the dataset
//by executing a SODA request
func (r *GetRequest) Count() (uint, error) {

	oldformat := r.Format
	oldorder := r.Query.Order
	oldselect := r.Query.Select
	defer func() {
		r.Format = oldformat
		r.Query.Order = oldorder
		r.Query.Select = oldselect
	}()

	r.Format = "json"
	r.Query.Select = []string{"count(*)"}
	r.Query.ClearOrder()

	resp, err := r.Get()
	if err != nil {
		return 0, err
	}
	defer resp.Body.Close()

	count := []struct {
		Count string
	}{}
	err = json.NewDecoder(resp.Body).Decode(&count)
	if err != nil {
		return 0, err
	}
	if len(count) == 0 {
		return 0, fmt.Errorf("Empty count response")
	}
	icount, err := strconv.Atoi(count[0].Count)
	if err != nil {
		return 0, err
	}
	return uint(icount), nil
}

//Fields returns all the fields present in the dataset (ignores select fields).
//Spaces in fieldnames are replaced by underscores.
func (r *GetRequest) Fields() ([]string, error) {

	oldformat := r.Format
	oldorder := r.Query.Order
	oldlimit := r.Query.Limit
	oldselect := r.Query.Select
	defer func() {
		r.Format = oldformat
		r.Query.Select = oldselect
		r.Query.Order = oldorder
		r.Query.Limit = oldlimit
	}()

	fields := []string{}

	r.Format = "csv"
	r.Query.Select = []string{}
	r.Query.Limit = 0
	r.Query.ClearOrder()

	resp, err := r.Get()
	if err != nil {
		return fields, err
	}
	defer resp.Body.Close()

	csvreader := csv.NewReader(resp.Body)
	record, err := csvreader.Read()
	if err != nil && err != io.EOF {
		return fields, err
	}
	for _, field := range record {
		fields = append(fields, strings.Replace(field, " ", "_", -1))
	}
	return fields, nil
}

//Modified returns when the dataset was last updated
func (r *GetRequest) Modified() (time.Time, error) {

	oldformat := r.Format
	oldorder := r.Query.Order
	oldlimit := r.Query.Limit
	oldselect := r.Query.Select
	defer func() {
		r.Format = oldformat
		r.Query.Select = oldselect
		r.Query.Order = oldorder
		r.Query.Limit = oldlimit
	}()

	r.Format = "json"
	r.Query.Select = []string{}
	r.Query.Limit = 0
	r.Query.ClearOrder()

	resp, err := r.Get()
	if err != nil {
		return time.Time{}, err
	}
	defer resp.Body.Close()

	lms := resp.Header.Get("X-Soda2-Truth-Last-Modified")
	if lms == "" {
		lms = resp.Header.Get("Last-Modified")
	}
	if lms == "" {
		return time.Time{}, fmt.Errorf("Cannot get last modified date, field not present in HTTP header")
	}

	return time.Parse(time.RFC1123, lms)
}

//SimpleFilters is the easiest way to filter columns for equality.
//Add the column to filter on a map key and the filter value as map value.
//If you include multiple filters, the filters will be combined using a boolean AND.
//See http://dev.socrata.com/docs/filtering.html
type SimpleFilters map[string]string

//URLValues returns the url.Values for the SimpleFilters
func (sf SimpleFilters) URLValues() url.Values {
	uv := make(url.Values)
	for key, val := range sf {
		uv.Add(key, val)
	}
	return uv
}

//SoSQL implements the Socrata Query Language and is used to build more complex queries.
//See http://dev.socrata.com/docs/queries.html
type SoSQL struct {
	Select []string //The set of columns to be returned. Default: All columns, equivalent to $select=*
	Where  string   //Filters the rows to be returned. Default: No filter, and returning a max of $limit values
	Order  []struct {
		Column string //Column name
		Desc   bool   //Descending. Default: false = Ascending
	} //Specifies the order of results. Default: Unspecified order, but it will be consistent across paging
	Group  string //Column to group results on, similar to SQL Grouping. Default: No grouping
	Limit  uint   //Maximum number of results to return. Default: 1000 (with a maximum of 50,000)
	Offset uint   //Offset count into the results to start at, used for paging. Default: 0
	Q      string //Performs a full text search for a value. Default: No search

}

//AddOrder can be called for each field you want to sort the result on.
//If parameter descending is true, the column will be sorted descending, or ascending if false.
func (sq *SoSQL) AddOrder(column string, descending bool) {
	sq.Order = append(sq.Order, struct {
		Column string
		Desc   bool
	}{column, descending})
}

//ClearOrder removes all order fields
func (sq *SoSQL) ClearOrder() {
	sq.Order = []struct {
		Column string
		Desc   bool
	}{}
}

//URLValues returns the url.Values for the SoSQL query
func (sq *SoSQL) URLValues() url.Values {
	uv := make(url.Values)
	if len(sq.Select) > 0 {
		uv.Add("$select", strings.Join(sq.Select, ","))
	}
	if len(sq.Where) > 0 {
		uv.Add("$where", sq.Where)
	}
	if len(sq.Order) > 0 {
		order := []string{}
		for _, o := range sq.Order {
			if o.Desc {
				order = append(order, o.Column+" DESC")
			} else {
				order = append(order, o.Column+" ASC")
			}
		}
		uv.Add("$order", strings.Join(order, ","))
	}
	if len(sq.Q) > 0 {
		uv.Add("$q", sq.Q)
	}
	if len(sq.Group) > 0 {
		uv.Add("$group", sq.Group)
	}
	if sq.Limit > 0 {
		uv.Add("$limit", fmt.Sprintf("%d", sq.Limit))
	}
	if sq.Offset > 0 {
		uv.Add("$offset", fmt.Sprintf("%d", sq.Offset))
	}
	return uv
}

//OffsetGetRequest is a request getter that gets all the records using the filters and limits from gr and
//is safe to use by multiple goroutines, use Next(number) to get the next number of records.
//A sync.WaitGroup is embedded for easy concurrency.
type OffsetGetRequest struct {
	gr     *GetRequest
	m      sync.Mutex
	offset uint
	count  uint
	sync.WaitGroup
}

//ErrDone is returned by OffsetGetRequest.Next when done
var ErrDone = fmt.Errorf("Done")

//Next gets the next number of records
func (o *OffsetGetRequest) Next(number uint) (*http.Response, error) {
	o.m.Lock() //lock to protect offset
	if o.IsDone() {
		o.m.Unlock()
		return nil, ErrDone
	}
	if len(o.gr.Query.Order) == 0 { //If offset is used we must specify an order
		return nil, fmt.Errorf("Cannot use an offset without setting the order")
	}
	if o.offset+number > o.count {
		number = o.count - o.offset
	}
	o.gr.Query.Offset = o.offset
	o.gr.Query.Limit = number
	rawquery := o.gr.URLValues().Encode()
	o.offset += number
	o.m.Unlock() //unlock before the request is done
	return get(o.gr, rawquery)
}

//Count returns the number of records from memory
func (o *OffsetGetRequest) Count() uint {
	return o.count
}

//IsDone returns if we have gotten all records
func (o *OffsetGetRequest) IsDone() bool {
	return o.offset >= o.count
}

//NewOffsetGetRequest creates a new OffsetGetRequest from gr
//and does a count request to determine the number of records to get
func NewOffsetGetRequest(gr *GetRequest) (*OffsetGetRequest, error) {
	count, err := gr.Count()
	if err != nil {
		return nil, err
	}
	return &OffsetGetRequest{gr: gr, offset: 0, count: count}, nil
}

//get is the function that executes the HTTP request
func get(r *GetRequest, rawquery string) (*http.Response, error) {

	client := http.DefaultClient
	if r.HttpClient != nil {
		client = r.HttpClient
	}

	req, err := http.NewRequest("GET", r.GetEndpoint(), nil)
	if err != nil {
		return nil, err
	}
	req.URL.RawQuery = rawquery
	req.Header.Set("X-App-Token", r.apptoken)

	//Execute
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode >= 400 {
		errormessage, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, err
		}
		return nil, fmt.Errorf("SODA error %d:\nURL: GET %s\nResponse: %s", resp.StatusCode, req.URL.String(), errormessage)
	}

	return resp, nil
}
<|endoftext|>


<commit_before>/*
 * Go Solr, a Solr library written in Go.
 * Original author Rich Taylor, 2012 - http://rsty.org/, http://github.com/rtt/
 * Released under the "do whatever the fuck you want" license. http://sam.zoy.org/wtfpl/
 */

package solr

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
)

/*
 * Represents a "connection"; actually just a host and port
 * (and probably at some point a Solr Core name)
 */
type Connection struct {
	Host    string
	Port    int
	Core    string
	Version []int
}

/*
 * Represents a Solr document, as returned by Select queries
 */
type Document struct {
	Fields map[string]interface{}
}

/*
 * Represents a FacetCount for a Facet
 */
type FacetCount struct {
	Value string
	Count int
}

/* chunked size of facet solr return format */
var facet_chunk_size int = 2

/*
 * Represents a Facet with a name and count
 */
type Facet struct {
	Name   string       // accepts_4x4s
	Counts []FacetCount // a set of values
}

/*
 * Represents a collection of solr documents
 * and various other metrics
 */
type DocumentCollection struct {
	Facets     []Facet
	Collection []Document
	NumFacets  int // convenience...
	NumFound   int
	Start      int
}

/*
 * Represents a Solr response
 */
type SelectResponse struct {
	Results *DocumentCollection
	Status  int
	QTime   int
	// TODO: Debug info as well?
}

/*
 * Represents an error from Solr
 */
type ErrorResponse struct {
	Message string
	Status  int
}

type UpdateResponse struct {
	Success bool
}

/*
 * Holds URL parameters
 */
type URLParamMap map[string][]string

/*
 * Query represents a query with various params
 */
type Query struct {
	Params     URLParamMap
	Rows       int
	Start      int
	Sort       string
	DefType    string
	Debug      bool
	OmitHeader bool
}

/*
 * Query.String() returns the Query in solr query string format
 */
func (q *Query) String() string {
	// TODO: this is kinda ugly
	s := []string{}

	if len(q.Params) > 0 {
		s = append(s, EncodeURLParamMap(&q.Params))
	}

	if q.Rows != 0 {
		s = append(s, fmt.Sprintf("rows=%d", q.Rows))
	}

	if q.Start != 0 {
		s = append(s, fmt.Sprintf("start=%d", q.Start))
	}

	if q.Sort != "" {
		s = append(s, fmt.Sprintf("sort=%s", q.Sort))
	}

	if q.DefType != "" {
		s = append(s, fmt.Sprintf("deftype=%s", q.DefType))
	}

	if q.Debug {
		s = append(s, fmt.Sprintf("debugQuery=true"))
	}

	if q.OmitHeader {
		s = append(s, fmt.Sprintf("omitHeader=true"))
	}

	return strings.Join(s, "&")
}

/*
 * DocumentCollection.Get() returns the document in the collection
 * at position i
 */
func (d *DocumentCollection) Get(i int) *Document {
	return &d.Collection[i]
}

/*
 * DocumentCollection.Len() returns the amount of documents
 * in the collection
 */
func (d *DocumentCollection) Len() int {
	return len(d.Collection)
}

/*
 * Document.Field() returns the value of the given field name in the document
 */
func (document Document) Field(field string) interface{} {
	r, _ := document.Fields[field]
	return r
}

/*
 * Document.Doc() returns the raw document (map)
 */
func (document Document) Doc() map[string]interface{} {
	return document.Fields
}

func (r SelectResponse) String() string {
	return fmt.Sprintf("SelectResponse: %d Results, Status: %d, QTime: %d", r.Results.Len(), r.Status, r.QTime)
}

func (r ErrorResponse) String() string {
	return fmt.Sprintf("Solr Error: [code: %d, msg: \"%s\"]", r.Status, r.Message)
}

func (r UpdateResponse) String() string {
	if r.Success {
		return fmt.Sprintf("UpdateResponse: OK")
	}
	return fmt.Sprintf("UpdateResponse: FAIL")
}

/*
 * Performs a GET request to the given url
 * Returns a []byte containing the response body
 */
func HTTPGet(httpUrl string) ([]byte, error) {

	r, err := http.Get(httpUrl)

	fmt.Println(httpUrl)

	if err != nil {
		return nil, err
	}

	defer r.Body.Close()

	if err != nil {
		return nil, fmt.Errorf("GET failed (%s)", httpUrl)
	}

	// read the response and check
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return nil, fmt.Errorf("Response read failed")
	}

	return body, nil
}

/*
 * Performs a HTTP Post request. Takes:
 * * A url
 * * Headers, in the format [][]string{} (e.g., [[key, val], [key, val], ...])
 * * A payload (post request body) which can be nil
 * * Returns the body of the response and an error if necessary
 */
func HTTPPost(url string, headers [][]string, payload *[]byte) ([]byte, error) {
	// setup post client
	client := &http.Client{}
	req, err := http.NewRequest("POST", url, bytes.NewReader(*payload))

	// add headers
	if len(headers) > 0 {
		for i := range headers {
			req.Header.Add(headers[i][0], headers[i][1])
		}
	}

	// perform request
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	if err != nil {
		return nil, fmt.Errorf(fmt.Sprintf("POST request failed: %s", err))
	}

	// read response, check & return
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	return body, nil
}

/*
 * Returns a URLEncoded version of a Param Map
 * E.g., ParamMap[foo:bar omg:wtf] => "foo=bar&omg=wtf"
 * TODO: This isn't exactly safe and there's probably a library pkg to do this already...
 */
func EncodeURLParamMap(m *URLParamMap) string {

	parameters := url.Values{}

	for k, v := range *m {
		l := len(v)
		for x := 0; x < l; x++ {
			parameters.Add(k, v[x])
		}

	}

	qEncoded := parameters.Encode()
	return string(qEncoded)

}

/*
 * Generates a Solr query string from a connection and a query string
 */
func SolrSelectString(c *Connection, q string) string {
	return fmt.Sprintf("http://%s:%d/solr/%s/select?wt=json&%s", c.Host, c.Port, c.Core, q)
}

/*
 * Generates a Solr update query string. Adds ?commit=true
 * if commit arg is true.
 */
func SolrUpdateString(c *Connection, commit bool) string {
	s := fmt.Sprintf("http://%s:%d/solr/update", c.Host, c.Port)
	if commit {
		return fmt.Sprintf("%s?commit=true", s)
	}
	return s
}

/*
 * Decodes a json formatted []byte into an interface{} type
 */
func BytesToJSON(b *[]byte) (*interface{}, error) {
	var container interface{}
	err := json.Unmarshal(*b, &container)

	if err != nil {
		return nil, fmt.Errorf("Response decode error")
	}

	return &container, nil
}

/*
 * Encodes a map[string]interface{} to bytes and returns
 * a pointer to said bytes
 */
func JSONToBytes(m map[string]interface{}) (*[]byte, error) {
	b, err := json.Marshal(m)
	if err != nil {
		return nil, fmt.Errorf("Failed to encode JSON")
	}

	return &b, nil
}

/*
 * Takes a JSON formatted Solr response (interface{}, not []byte)
 * And returns a *Response
 */
func BuildResponse(j *interface{}) (*SelectResponse, error) {

	// look for a response element, bail if not present
	response_root := (*j).(map[string]interface{})
	response := response_root["response"]
	if response == nil {
		return nil, fmt.Errorf("Supplied interface appears invalid (missing response)")
	}

	// begin Response creation
	r := SelectResponse{}

	// do status & qtime, if possible
	r_header := (*j).(map[string]interface{})["responseHeader"].(map[string]interface{})
	if r_header != nil {
		r.Status = int(r_header["status"].(float64))
		r.QTime = int(r_header["QTime"].(float64))
	}

	// now do docs, if they exist in the response
	docs := response.(map[string]interface{})["docs"].([]interface{})
	if docs != nil {
		// the total amount of results, irrespective of the amount returned in the response
		num_found := int(response.(map[string]interface{})["numFound"].(float64))

		// and the amount actually returned
		num_results := len(docs)

		coll := DocumentCollection{}
		coll.NumFound = num_found

		ds := []Document{}

		for i := 0; i < num_results; i++ {
			ds = append(ds, Document{docs[i].(map[string]interface{})})
		}

		coll.Collection = ds
		r.Results = &coll
	}

	// facets
	facet_response, ok := response_root["facet_counts"].(interface{})
	if ok == true {
		facet_counts := facet_response.(map[string]interface{})
		if facet_counts != nil {
			// do counts if they exist
			facet_fields := facet_counts["facet_fields"].(map[string]interface{})
			facets := []Facet{}
			if facet_fields != nil {
				// iterate over each facet field, create facet & counts for each field
				for k, v := range facet_fields {
					f := Facet{Name: k}
					chunked := chunk(v.([]interface{}), facet_chunk_size)
					lc := len(chunked)
					for i := 0; i < lc; i++ {
						f.Counts = append(f.Counts, FacetCount{
							Value: chunked[i][0].(string),
							Count: int(chunked[i][1].(float64)),
						})
					}
					facets = append(facets, f)
				}
			}

			// add Facets to collection
			r.Results.Facets = facets
			r.Results.NumFacets = len(facets)

		}
	}

	return &r, nil
}

/*
 * Decodes a HTTP (Solr) response and returns a Response
 */
func SelectResponseFromHTTPResponse(b []byte) (*SelectResponse, error) {
	j, err := BytesToJSON(&b)
	if err != nil {
		return nil, fmt.Errorf("Unable to decode")
	}

	resp, err := BuildResponse(j)
	if err != nil {
		return nil, fmt.Errorf("Error building response")
	}

	return resp, nil
}

/*
 * Determines whether a decoded response from Solr
 * is an error response or not. Returns a bool (true if error)
 * and an ErrorResponse (if the response is an error response)
 * otherwise nil
 */
func SolrErrorResponse(m map[string]interface{}) (bool, *ErrorResponse) {
	// check for existance of "error" key
	if _, found := m["error"]; found {
		error := m["error"].(map[string]interface{})
		return true, &ErrorResponse{
			Message: error["msg"].(string),
			Status:  int(error["code"].(float64)),
		}
	}
	return false, nil
}

/*
 * Similar to python's itertools.izip_longest;
 * takes an array and chunks it according to a given splice size
 * eg: chnunk([1,2,3,4,5,6], 2) == [[1,2], [3,4], [5,6]]
 */
func chunk(s []interface{}, sz int) [][]interface{} {
	r := [][]interface{}{}
	j := len(s)
	for i := 0; i < j; i += sz {
		r = append(r, s[i:i+sz])
	}
	return r
}

/*
 * Inits a new Connection to a Solr instance
 * Note: this doesn't actually hold a connection, its just
 *       a container for holding a hostname & port
 */
func Init(host string, port int, core string) (*Connection, error) {

	if len(host) == 0 {
		return nil, fmt.Errorf("Invalid hostname (must be length >= 1)")
	}

	if port <= 0 || port > 65535 {
		return nil, fmt.Errorf("Invalid port (must be 1..65535")
	}

	return &Connection{Host: host, Port: port, Core: core}, nil
}

/*
 * Performs a Select query given a Query
 */
func (c *Connection) Select(q *Query) (*SelectResponse, error) {
	body, err := HTTPGet(SolrSelectString(c, q.String()))

	if err != nil {
		return nil, fmt.Errorf("Some sort of http failure") // TODO: investigate how net/http fails
	}

	r, err := SelectResponseFromHTTPResponse(body)

	if err != nil {
		return nil, err
	}

	return r, nil
}

/*
 * Performs a raw Select query given a raw query string
 */
func (c *Connection) SelectRaw(q string) (*SelectResponse, error) {
	body, err := HTTPGet(SolrSelectString(c, q))

	if err != nil {
		return nil, fmt.Errorf("Some sort of http failure") // TODO: investigate how net/http fails
	}

	r, err := SelectResponseFromHTTPResponse(body)

	if err != nil {
		return nil, err
	}

	return r, nil
}

/*
 * Performs a Solr Update query against a given update document
 * specified in a map[string]interface{} type
 * NOTE: Requires JSON updates to be enabled, see;
 * http://wiki.apache.org/solr/UpdateJSON
 * FUTURE: Will ask for solr version details in Connection and
 * act appropriately
 */
func (c *Connection) Update(m map[string]interface{}, commit bool) (*UpdateResponse, error) {

	// encode "json" to a byte array & check
	payload, err := JSONToBytes(m)
	if err != nil {
		return nil, err
	}

	// perform request
	resp, err := HTTPPost(
		SolrUpdateString(c, commit),
		[][]string{{"Content-Type", "application/json"}},
		payload)

	if err != nil {
		return nil, err
	}

	// decode the response & check
	decoded, err := BytesToJSON(&resp)
	if err != nil {
		return nil, err
	}

	error, report := SolrErrorResponse((*decoded).(map[string]interface{}))
	if error {
		return nil, fmt.Errorf(fmt.Sprintf("%s", *report))
	}

	return &UpdateResponse{true}, nil
}

// func (c *Connection) Commit() (*UpdateResponse, error) {

// }
<commit_msg>Added core to update string, and removed extraneous Println<commit_after>/*
 * Go Solr, a Solr library written in Go.
 * Original author Rich Taylor, 2012 - http://rsty.org/, http://github.com/rtt/
 * Released under the "do whatever the fuck you want" license. http://sam.zoy.org/wtfpl/
 */

package solr

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
)

/*
 * Represents a "connection"; actually just a host and port
 * (and probably at some point a Solr Core name)
 */
type Connection struct {
	Host    string
	Port    int
	Core    string
	Version []int
}

/*
 * Represents a Solr document, as returned by Select queries
 */
type Document struct {
	Fields map[string]interface{}
}

/*
 * Represents a FacetCount for a Facet
 */
type FacetCount struct {
	Value string
	Count int
}

/* chunked size of facet solr return format */
var facet_chunk_size int = 2

/*
 * Represents a Facet with a name and count
 */
type Facet struct {
	Name   string       // accepts_4x4s
	Counts []FacetCount // a set of values
}

/*
 * Represents a collection of solr documents
 * and various other metrics
 */
type DocumentCollection struct {
	Facets     []Facet
	Collection []Document
	NumFacets  int // convenience...
	NumFound   int
	Start      int
}

/*
 * Represents a Solr response
 */
type SelectResponse struct {
	Results *DocumentCollection
	Status  int
	QTime   int
	// TODO: Debug info as well?
}

/*
 * Represents an error from Solr
 */
type ErrorResponse struct {
	Message string
	Status  int
}

type UpdateResponse struct {
	Success bool
}

/*
 * Holds URL parameters
 */
type URLParamMap map[string][]string

/*
 * Query represents a query with various params
 */
type Query struct {
	Params     URLParamMap
	Rows       int
	Start      int
	Sort       string
	DefType    string
	Debug      bool
	OmitHeader bool
}

/*
 * Query.String() returns the Query in solr query string format
 */
func (q *Query) String() string {
	// TODO: this is kinda ugly
	s := []string{}

	if len(q.Params) > 0 {
		s = append(s, EncodeURLParamMap(&q.Params))
	}

	if q.Rows != 0 {
		s = append(s, fmt.Sprintf("rows=%d", q.Rows))
	}

	if q.Start != 0 {
		s = append(s, fmt.Sprintf("start=%d", q.Start))
	}

	if q.Sort != "" {
		s = append(s, fmt.Sprintf("sort=%s", q.Sort))
	}

	if q.DefType != "" {
		s = append(s, fmt.Sprintf("deftype=%s", q.DefType))
	}

	if q.Debug {
		s = append(s, fmt.Sprintf("debugQuery=true"))
	}

	if q.OmitHeader {
		s = append(s, fmt.Sprintf("omitHeader=true"))
	}

	return strings.Join(s, "&")
}

/*
 * DocumentCollection.Get() returns the document in the collection
 * at position i
 */
func (d *DocumentCollection) Get(i int) *Document {
	return &d.Collection[i]
}

/*
 * DocumentCollection.Len() returns the amount of documents
 * in the collection
 */
func (d *DocumentCollection) Len() int {
	return len(d.Collection)
}

/*
 * Document.Field() returns the value of the given field name in the document
 */
func (document Document) Field(field string) interface{} {
	r, _ := document.Fields[field]
	return r
}

/*
 * Document.Doc() returns the raw document (map)
 */
func (document Document) Doc() map[string]interface{} {
	return document.Fields
}

func (r SelectResponse) String() string {
	return fmt.Sprintf("SelectResponse: %d Results, Status: %d, QTime: %d", r.Results.Len(), r.Status, r.QTime)
}

func (r ErrorResponse) String() string {
	return fmt.Sprintf("Solr Error: [code: %d, msg: \"%s\"]", r.Status, r.Message)
}

func (r UpdateResponse) String() string {
	if r.Success {
		return fmt.Sprintf("UpdateResponse: OK")
	}
	return fmt.Sprintf("UpdateResponse: FAIL")
}

/*
 * Performs a GET request to the given url
 * Returns a []byte containing the response body
 */
func HTTPGet(httpUrl string) ([]byte, error) {

	r, err := http.Get(httpUrl)

	if err != nil {
		return nil, err
	}

	defer r.Body.Close()

	if err != nil {
		return nil, fmt.Errorf("GET failed (%s)", httpUrl)
	}

	// read the response and check
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return nil, fmt.Errorf("Response read failed")
	}

	return body, nil
}

/*
 * Performs a HTTP Post request. Takes:
 * * A url
 * * Headers, in the format [][]string{} (e.g., [[key, val], [key, val], ...])
 * * A payload (post request body) which can be nil
 * * Returns the body of the response and an error if necessary
 */
func HTTPPost(url string, headers [][]string, payload *[]byte) ([]byte, error) {
	// setup post client
	client := &http.Client{}
	req, err := http.NewRequest("POST", url, bytes.NewReader(*payload))

	// add headers
	if len(headers) > 0 {
		for i := range headers {
			req.Header.Add(headers[i][0], headers[i][1])
		}
	}

	// perform request
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	if err != nil {
		return nil, fmt.Errorf(fmt.Sprintf("POST request failed: %s", err))
	}

	// read response, check & return
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	return body, nil
}

/*
 * Returns a URLEncoded version of a Param Map
 * E.g., ParamMap[foo:bar omg:wtf] => "foo=bar&omg=wtf"
 * TODO: This isn't exactly safe and there's probably a library pkg to do this already...
 */
func EncodeURLParamMap(m *URLParamMap) string {

	parameters := url.Values{}

	for k, v := range *m {
		l := len(v)
		for x := 0; x < l; x++ {
			parameters.Add(k, v[x])
		}

	}

	qEncoded := parameters.Encode()
	return string(qEncoded)

}

/*
 * Generates a Solr query string from a connection and a query string
 */
func SolrSelectString(c *Connection, q string) string {
	return fmt.Sprintf("http://%s:%d/solr/%s/select?wt=json&%s", c.Host, c.Port, c.Core, q)
}

/*
 * Generates a Solr update query string. Adds ?commit=true
 * if commit arg is true.
 */
func SolrUpdateString(c *Connection, commit bool) string {
	s := fmt.Sprintf("http://%s:%d/solr/%s/update", c.Host, c.Port, c.Core)
	if commit {
		return fmt.Sprintf("%s?commit=true", s)
	}
	return s
}

/*
 * Decodes a json formatted []byte into an interface{} type
 */
func BytesToJSON(b *[]byte) (*interface{}, error) {
	var container interface{}
	err := json.Unmarshal(*b, &container)

	if err != nil {
		return nil, fmt.Errorf("Response decode error")
	}

	return &container, nil
}

/*
 * Encodes a map[string]interface{} to bytes and returns
 * a pointer to said bytes
 */
func JSONToBytes(m map[string]interface{}) (*[]byte, error) {
	b, err := json.Marshal(m)
	if err != nil {
		return nil, fmt.Errorf("Failed to encode JSON")
	}

	return &b, nil
}

/*
 * Takes a JSON formatted Solr response (interface{}, not []byte)
 * And returns a *Response
 */
func BuildResponse(j *interface{}) (*SelectResponse, error) {

	// look for a response element, bail if not present
	response_root := (*j).(map[string]interface{})
	response := response_root["response"]
	if response == nil {
		return nil, fmt.Errorf("Supplied interface appears invalid (missing response)")
	}

	// begin Response creation
	r := SelectResponse{}

	// do status & qtime, if possible
	r_header := (*j).(map[string]interface{})["responseHeader"].(map[string]interface{})
	if r_header != nil {
		r.Status = int(r_header["status"].(float64))
		r.QTime = int(r_header["QTime"].(float64))
	}

	// now do docs, if they exist in the response
	docs := response.(map[string]interface{})["docs"].([]interface{})
	if docs != nil {
		// the total amount of results, irrespective of the amount returned in the response
		num_found := int(response.(map[string]interface{})["numFound"].(float64))

		// and the amount actually returned
		num_results := len(docs)

		coll := DocumentCollection{}
		coll.NumFound = num_found

		ds := []Document{}

		for i := 0; i < num_results; i++ {
			ds = append(ds, Document{docs[i].(map[string]interface{})})
		}

		coll.Collection = ds
		r.Results = &coll
	}

	// facets
	facet_response, ok := response_root["facet_counts"].(interface{})
	if ok == true {
		facet_counts := facet_response.(map[string]interface{})
		if facet_counts != nil {
			// do counts if they exist
			facet_fields := facet_counts["facet_fields"].(map[string]interface{})
			facets := []Facet{}
			if facet_fields != nil {
				// iterate over each facet field, create facet & counts for each field
				for k, v := range facet_fields {
					f := Facet{Name: k}
					chunked := chunk(v.([]interface{}), facet_chunk_size)
					lc := len(chunked)
					for i := 0; i < lc; i++ {
						f.Counts = append(f.Counts, FacetCount{
							Value: chunked[i][0].(string),
							Count: int(chunked[i][1].(float64)),
						})
					}
					facets = append(facets, f)
				}
			}

			// add Facets to collection
			r.Results.Facets = facets
			r.Results.NumFacets = len(facets)

		}
	}

	return &r, nil
}

/*
 * Decodes a HTTP (Solr) response and returns a Response
 */
func SelectResponseFromHTTPResponse(b []byte) (*SelectResponse, error) {
	j, err := BytesToJSON(&b)
	if err != nil {
		return nil, fmt.Errorf("Unable to decode")
	}

	resp, err := BuildResponse(j)
	if err != nil {
		return nil, fmt.Errorf("Error building response")
	}

	return resp, nil
}

/*
 * Determines whether a decoded response from Solr
 * is an error response or not. Returns a bool (true if error)
 * and an ErrorResponse (if the response is an error response)
 * otherwise nil
 */
func SolrErrorResponse(m map[string]interface{}) (bool, *ErrorResponse) {
	// check for existance of "error" key
	if _, found := m["error"]; found {
		error := m["error"].(map[string]interface{})
		return true, &ErrorResponse{
			Message: error["msg"].(string),
			Status:  int(error["code"].(float64)),
		}
	}
	return false, nil
}

/*
 * Similar to python's itertools.izip_longest;
 * takes an array and chunks it according to a given splice size
 * eg: chnunk([1,2,3,4,5,6], 2) == [[1,2], [3,4], [5,6]]
 */
func chunk(s []interface{}, sz int) [][]interface{} {
	r := [][]interface{}{}
	j := len(s)
	for i := 0; i < j; i += sz {
		r = append(r, s[i:i+sz])
	}
	return r
}

/*
 * Inits a new Connection to a Solr instance
 * Note: this doesn't actually hold a connection, its just
 *       a container for holding a hostname & port
 */
func Init(host string, port int, core string) (*Connection, error) {

	if len(host) == 0 {
		return nil, fmt.Errorf("Invalid hostname (must be length >= 1)")
	}

	if port <= 0 || port > 65535 {
		return nil, fmt.Errorf("Invalid port (must be 1..65535")
	}

	return &Connection{Host: host, Port: port, Core: core}, nil
}

/*
 * Performs a Select query given a Query
 */
func (c *Connection) Select(q *Query) (*SelectResponse, error) {
	body, err := HTTPGet(SolrSelectString(c, q.String()))

	if err != nil {
		return nil, fmt.Errorf("Some sort of http failure") // TODO: investigate how net/http fails
	}

	r, err := SelectResponseFromHTTPResponse(body)

	if err != nil {
		return nil, err
	}

	return r, nil
}

/*
 * Performs a raw Select query given a raw query string
 */
func (c *Connection) SelectRaw(q string) (*SelectResponse, error) {
	body, err := HTTPGet(SolrSelectString(c, q))

	if err != nil {
		return nil, fmt.Errorf("Some sort of http failure") // TODO: investigate how net/http fails
	}

	r, err := SelectResponseFromHTTPResponse(body)

	if err != nil {
		return nil, err
	}

	return r, nil
}

/*
 * Performs a Solr Update query against a given update document
 * specified in a map[string]interface{} type
 * NOTE: Requires JSON updates to be enabled, see;
 * http://wiki.apache.org/solr/UpdateJSON
 * FUTURE: Will ask for solr version details in Connection and
 * act appropriately
 */
func (c *Connection) Update(m map[string]interface{}, commit bool) (*UpdateResponse, error) {

	// encode "json" to a byte array & check
	payload, err := JSONToBytes(m)
	if err != nil {
		return nil, err
	}

	// perform request
	resp, err := HTTPPost(
		SolrUpdateString(c, commit),
		[][]string{{"Content-Type", "application/json"}},
		payload)

	if err != nil {
		return nil, err
	}

	// decode the response & check
	decoded, err := BytesToJSON(&resp)
	if err != nil {
		return nil, err
	}

	error, report := SolrErrorResponse((*decoded).(map[string]interface{}))
	if error {
		return nil, fmt.Errorf(fmt.Sprintf("%s", *report))
	}

	return &UpdateResponse{true}, nil
}

// func (c *Connection) Commit() (*UpdateResponse, error) {

// }
<|endoftext|>


<commit_before>package remote

import (
	"errors"
	"fmt"
	"time"

	"github.com/AsynkronIT/protoactor-go/actor"
	"github.com/AsynkronIT/protoactor-go/log"
)

var (
	nameLookup   = make(map[string]actor.Props)
	activatorPid *actor.PID
)

func spawnActivatorActor() {
	activatorPid, _ = actor.SpawnNamed(actor.FromProducer(newActivatorActor()), "activator")
}

func stopActivatorActor() {
	activatorPid.GracefulStop()
}

//Register a known actor props by name
func Register(kind string, props *actor.Props) {
	nameLookup[kind] = *props
}

//GetKnownKinds returns a slice of known actor "kinds"
func GetKnownKinds() []string {
	keys := make([]string, 0, len(nameLookup))
	for k := range nameLookup {
		keys = append(keys, k)
	}
	return keys
}

type activator struct {
}

var ErrActivatorUnavailable = &ActivatorError{ResponseStatusCodeUNAVAILABLE.ToInt32(), true}

type ActivatorError struct {
	Code       int32
	DoNotPanic bool
}

func (e *ActivatorError) Error() string {
	return fmt.Sprint(e.Code)
}

//ActivatorForAddress returns a PID for the activator at the given address
func ActivatorForAddress(address string) *actor.PID {
	pid := actor.NewPID(address, "activator")
	return pid
}

//SpawnFuture spawns a remote actor and returns a Future that completes once the actor is started
func SpawnFuture(address, name, kind string, timeout time.Duration) *actor.Future {
	activator := ActivatorForAddress(address)
	f := activator.RequestFuture(&ActorPidRequest{
		Name: name,
		Kind: kind,
	}, timeout)
	return f
}

//Spawn spawns a remote actor of a given type at a given address
func Spawn(address, kind string, timeout time.Duration) (*ActorPidResponse, error) {
	return SpawnNamed(address, "", kind, timeout)
}

//SpawnNamed spawns a named remote actor of a given type at a given address
func SpawnNamed(address, name, kind string, timeout time.Duration) (*ActorPidResponse, error) {
	activator := ActivatorForAddress(address)
	res, err := activator.RequestFuture(&ActorPidRequest{
		Name: name,
		Kind: kind,
	}, timeout).Result()
	if err != nil {
		return nil, errors.New("remote: Remote activating timed out")
	}
	switch msg := res.(type) {
	case *ActorPidResponse:
		return msg, nil
	default:
		return nil, errors.New("remote: Unknown response when remote activating")
	}
}

func newActivatorActor() actor.Producer {
	return func() actor.Actor {
		return &activator{}
	}
}

func (*activator) Receive(context actor.Context) {
	switch msg := context.Message().(type) {
	case *actor.Started:
		plog.Debug("Started Activator")
	case *ActorPidRequest:
		props := nameLookup[msg.Kind]
		name := msg.Name

		//unnamed actor, assign auto ID
		if name == "" {
			name = actor.ProcessRegistry.NextId()
		}

		pid, err := actor.SpawnNamed(&props, "Remote$"+name)

		if err == nil {
			response := &ActorPidResponse{Pid: pid}
			context.Respond(response)
		} else if aErr, ok := err.(*ActivatorError); ok {
			response := &ActorPidResponse{StatusCode: aErr.Code}
			context.Respond(response)
			if !aErr.DoNotPanic {
				panic(err)
			}
		} else {
			response := &ActorPidResponse{StatusCode: ResponseStatusCodeERROR.ToInt32()}
			context.Respond(response)
			panic(err)
		}
	case actor.SystemMessage:
		//ignore
	default:
		plog.Error("Activator received unknown message", log.Message(msg))
	}
}
<commit_msg>Process ErrNameExists error, and return existed process PID with err status code.<commit_after>package remote

import (
	"errors"
	"fmt"
	"time"

	"github.com/AsynkronIT/protoactor-go/actor"
	"github.com/AsynkronIT/protoactor-go/log"
)

var (
	nameLookup   = make(map[string]actor.Props)
	activatorPid *actor.PID
)

func spawnActivatorActor() {
	activatorPid, _ = actor.SpawnNamed(actor.FromProducer(newActivatorActor()), "activator")
}

func stopActivatorActor() {
	activatorPid.GracefulStop()
}

//Register a known actor props by name
func Register(kind string, props *actor.Props) {
	nameLookup[kind] = *props
}

//GetKnownKinds returns a slice of known actor "kinds"
func GetKnownKinds() []string {
	keys := make([]string, 0, len(nameLookup))
	for k := range nameLookup {
		keys = append(keys, k)
	}
	return keys
}

type activator struct {
}

//ErrActivatorUnavailable : this error will not panic the Activator.
//It simply tells Partition this Activator is not available
//Partition will then find next available Activator to spawn
var ErrActivatorUnavailable = &ActivatorError{ResponseStatusCodeUNAVAILABLE.ToInt32(), true}

type ActivatorError struct {
	Code       int32
	DoNotPanic bool
}

func (e *ActivatorError) Error() string {
	return fmt.Sprint(e.Code)
}

//ActivatorForAddress returns a PID for the activator at the given address
func ActivatorForAddress(address string) *actor.PID {
	pid := actor.NewPID(address, "activator")
	return pid
}

//SpawnFuture spawns a remote actor and returns a Future that completes once the actor is started
func SpawnFuture(address, name, kind string, timeout time.Duration) *actor.Future {
	activator := ActivatorForAddress(address)
	f := activator.RequestFuture(&ActorPidRequest{
		Name: name,
		Kind: kind,
	}, timeout)
	return f
}

//Spawn spawns a remote actor of a given type at a given address
func Spawn(address, kind string, timeout time.Duration) (*ActorPidResponse, error) {
	return SpawnNamed(address, "", kind, timeout)
}

//SpawnNamed spawns a named remote actor of a given type at a given address
func SpawnNamed(address, name, kind string, timeout time.Duration) (*ActorPidResponse, error) {
	activator := ActivatorForAddress(address)
	res, err := activator.RequestFuture(&ActorPidRequest{
		Name: name,
		Kind: kind,
	}, timeout).Result()
	if err != nil {
		return nil, errors.New("remote: Remote activating timed out")
	}
	switch msg := res.(type) {
	case *ActorPidResponse:
		return msg, nil
	default:
		return nil, errors.New("remote: Unknown response when remote activating")
	}
}

func newActivatorActor() actor.Producer {
	return func() actor.Actor {
		return &activator{}
	}
}

func (*activator) Receive(context actor.Context) {
	switch msg := context.Message().(type) {
	case *actor.Started:
		plog.Debug("Started Activator")
	case *ActorPidRequest:
		props := nameLookup[msg.Kind]
		name := msg.Name

		//unnamed actor, assign auto ID
		if name == "" {
			name = actor.ProcessRegistry.NextId()
		}

		pid, err := actor.SpawnNamed(&props, "Remote$"+name)

		if err == nil {
			response := &ActorPidResponse{Pid: pid}
			context.Respond(response)
		} else if err == actor.ErrNameExists {
			response := &ActorPidResponse{
				Pid: pid,
				StatusCode: ResponseStatusCodePROCESSNAMEALREADYEXIST.ToInt32(),
			}
			context.Respond(response)
			panic(err)
		} else if aErr, ok := err.(*ActivatorError); ok {
			response := &ActorPidResponse{
				StatusCode: aErr.Code,
			}
			context.Respond(response)
			if !aErr.DoNotPanic {
				panic(err)
			}
		} else {
			response := &ActorPidResponse{
				StatusCode: ResponseStatusCodeERROR.ToInt32(),
			}
			context.Respond(response)
			panic(err)
		}
	case actor.SystemMessage:
		//ignore
	default:
		plog.Error("Activator received unknown message", log.Message(msg))
	}
}
<|endoftext|>


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

package version

import (
	"context"
	"errors"
	"reflect"
	"testing"
	"time"

	"github.com/google/uuid"
	eh "github.com/looplab/eventhorizon"
	"github.com/looplab/eventhorizon/mocks"
	"github.com/looplab/eventhorizon/repo"
	"github.com/looplab/eventhorizon/repo/memory"
)

// NOTE: Not named "Integration" to enable running with the unit tests.
func TestReadRepo(t *testing.T) {
	baseRepo := memory.NewRepo()
	baseRepo.SetEntityFactory(func() eh.Entity {
		return &mocks.Model{}
	})

	r := NewRepo(baseRepo)
	if r == nil {
		t.Error("there should be a repository")
	}
	if parent := r.Parent(); parent != baseRepo {
		t.Error("the parent repo should be correct:", parent)
	}

	// Read repository with default namespace.
	repo.AcceptanceTest(t, context.Background(), r)
	extraRepoTests(t, context.Background(), r, baseRepo)

	// Read repository with other namespace.
	ctx := eh.NewContextWithNamespace(context.Background(), "ns")
	repo.AcceptanceTest(t, ctx, r)
	extraRepoTests(t, ctx, r, baseRepo)

}

func extraRepoTests(t *testing.T, ctx context.Context, r *Repo, baseRepo *memory.Repo) {
	// Set a model without version for the first test case.
	// A repo should not change models mid-life like this.
	baseRepo.SetEntityFactory(func() eh.Entity {
		return &mocks.SimpleModel{}
	})
	// Insert a non-versioned item.
	simpleModel := &mocks.SimpleModel{
		ID:      uuid.New(),
		Content: "simpleModel",
	}
	if err := r.Save(ctx, simpleModel); err != nil {
		t.Error("there should be no error:", err)
	}

	// Find with min version without version.
	ctxVersion := NewContextWithMinVersion(ctx, 1)
	model, err := r.Find(ctxVersion, simpleModel.ID)
	var repoErr eh.RepoError
	if !errors.As(err, &repoErr) || !errors.Is(err, eh.ErrEntityHasNoVersion) {
		t.Error("there should be a model has no version error:", err)
	}

	// Reset back to the model with version.
	baseRepo.SetEntityFactory(func() eh.Entity {
		return &mocks.Model{}
	})
	// Insert a versioned item.
	m1 := &mocks.Model{
		ID:        uuid.New(),
		Version:   1,
		Content:   "m1",
		CreatedAt: time.Now().Round(time.Millisecond),
	}
	if err := r.Save(ctx, m1); err != nil {
		t.Error("there should be no error:", err)
	}

	// Find with min version, too low.
	ctxVersion = NewContextWithMinVersion(ctx, 2)
	model, err = r.Find(ctxVersion, m1.ID)
	if !errors.As(err, &repoErr) || !errors.Is(err, eh.ErrIncorrectEntityVersion) {
		t.Error("there should be a incorrect model version error:", err)
	}

	// Find with min version, exactly.
	m1.Version = 2
	if err := r.Save(ctx, m1); err != nil {
		t.Error("there should be no error:", err)
	}
	ctxVersion = NewContextWithMinVersion(ctx, 2)
	model, err = r.Find(ctxVersion, m1.ID)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if !reflect.DeepEqual(model, m1) {
		t.Error("the item should be correct:", model)
	}

	// Find with min version, higher.
	m1.Version = 3
	if err := r.Save(ctx, m1); err != nil {
		t.Error("there should be no error:", err)
	}
	ctxVersion = NewContextWithMinVersion(ctx, 2)
	model, err = r.Find(ctxVersion, m1.ID)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if !reflect.DeepEqual(model, m1) {
		t.Error("the item should be correct:", model)
	}

	// Find with min version, with timeout, data available immediately.
	m1.Version = 4
	if err := r.Save(ctx, m1); err != nil {
		t.Error("there should be no error:", err)
	}
	ctxVersion = NewContextWithMinVersion(ctx, 4)
	var cancel func()
	ctxVersion, cancel = context.WithTimeout(ctxVersion, time.Second)
	defer cancel()
	model, err = r.Find(ctxVersion, m1.ID)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if !reflect.DeepEqual(model, m1) {
		t.Error("the item should be correct:", model)
	}

	// Find with min version, with timeout, data available on retry.
	go func() {
		<-time.After(100 * time.Millisecond)
		m1.Version = 5
		if err := r.Save(ctx, m1); err != nil {
			t.Error("there should be no error:", err)
		}
	}()
	ctxVersion = NewContextWithMinVersion(ctx, 5)
	ctxVersion, cancel = context.WithTimeout(ctxVersion, time.Second)
	defer cancel()
	model, err = r.Find(ctxVersion, m1.ID)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if !reflect.DeepEqual(model, m1) {
		t.Error("the item should be correct:", model)
	}

	// Find with min version, with timeout, data available immediately.
	m2 := &mocks.Model{
		ID:        uuid.New(),
		Version:   1,
		Content:   "m2",
		CreatedAt: time.Now().Round(time.Millisecond),
	}
	if err := r.Save(ctx, m2); err != nil {
		t.Error("there should be no error:", err)
	}
	ctxVersion = NewContextWithMinVersion(ctx, 1)
	ctxVersion, cancel = context.WithTimeout(ctxVersion, time.Second)
	defer cancel()
	// Meassure the time it takes, it should not wait > 100ms (the first retry).
	t1 := time.Now()
	model, err = r.Find(ctxVersion, m2.ID)
	dt := time.Now().Sub(t1)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if dt > 10*time.Millisecond {
		t.Error("the result should be available without delay")
	}
	if !reflect.DeepEqual(model, m2) {
		t.Error("the item should be correct:", model)
	}

	// Find with min version, with timeout, created data available on retry.
	m3 := &mocks.Model{
		ID:        uuid.New(),
		Version:   1,
		Content:   "m3",
		CreatedAt: time.Now().Round(time.Millisecond),
	}
	go func() {
		<-time.After(100 * time.Millisecond)
		if err := r.Save(ctx, m3); err != nil {
			t.Error("there should be no error:", err)
		}
	}()
	ctxVersion = NewContextWithMinVersion(ctx, 1)
	ctxVersion, cancel = context.WithTimeout(ctxVersion, time.Second)
	defer cancel()
	model, err = r.Find(ctxVersion, m3.ID)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if !reflect.DeepEqual(model, m3) {
		t.Error("the item should be correct:", model)
	}

	// Find with min version, with timeout exceeded.
	go func() {
		<-time.After(100 * time.Millisecond)
		m3.Version = 6
		if err := r.Save(ctx, m3); err != nil {
			t.Error("there should be no error:", err)
		}
	}()
	ctxVersion = NewContextWithMinVersion(ctx, 6)
	ctxVersion, cancel = context.WithTimeout(ctxVersion, 10*time.Millisecond)
	defer cancel()
	model, err = r.Find(ctxVersion, m3.ID)
	if !errors.Is(err, context.DeadlineExceeded) {
		t.Error("there should be a deadline exceeded error:", err)
	}

	// Find with min version, with timeout, created data available on retry.
	m4 := &mocks.Model{
		ID:        uuid.New(),
		Version:   4,
		Content:   "modelMinVersion",
		CreatedAt: time.Now().Round(time.Millisecond),
	}
	go func() {
		<-time.After(100 * time.Millisecond)
		if err := r.Save(ctx, m4); err != nil {
			t.Error("there should be no error:", err)
		}
	}()
	ctxVersion = NewContextWithMinVersion(ctx, 4)
	ctxVersion, cancel = context.WithTimeout(ctxVersion, time.Second)
	defer cancel()
	model, err = r.Find(ctxVersion, m4.ID)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if !reflect.DeepEqual(model, m4) {
		t.Error("the item should be correct:", model)
	}
}

func TestRepository(t *testing.T) {
	if r := Repository(nil); r != nil {
		t.Error("the parent repository should be nil:", r)
	}

	inner := &mocks.Repo{}
	if r := Repository(inner); r != nil {
		t.Error("the parent repository should be nil:", r)
	}

	repo := NewRepo(inner)
	outer := &mocks.Repo{ParentRepo: repo}
	if r := Repository(outer); r != repo {
		t.Error("the parent repository should be correct:", r)
	}
}
<commit_msg>Fix rounding bug for time<commit_after>// Copyright (c) 2014 - The Event Horizon authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package version

import (
	"context"
	"errors"
	"reflect"
	"testing"
	"time"

	"github.com/google/uuid"
	eh "github.com/looplab/eventhorizon"
	"github.com/looplab/eventhorizon/mocks"
	"github.com/looplab/eventhorizon/repo"
	"github.com/looplab/eventhorizon/repo/memory"
)

// NOTE: Not named "Integration" to enable running with the unit tests.
func TestReadRepo(t *testing.T) {
	baseRepo := memory.NewRepo()
	baseRepo.SetEntityFactory(func() eh.Entity {
		return &mocks.Model{}
	})

	r := NewRepo(baseRepo)
	if r == nil {
		t.Error("there should be a repository")
	}
	if parent := r.Parent(); parent != baseRepo {
		t.Error("the parent repo should be correct:", parent)
	}

	// Read repository with default namespace.
	repo.AcceptanceTest(t, context.Background(), r)
	extraRepoTests(t, context.Background(), r, baseRepo)

	// Read repository with other namespace.
	ctx := eh.NewContextWithNamespace(context.Background(), "ns")
	repo.AcceptanceTest(t, ctx, r)
	extraRepoTests(t, ctx, r, baseRepo)

}

func extraRepoTests(t *testing.T, ctx context.Context, r *Repo, baseRepo *memory.Repo) {
	// Set a model without version for the first test case.
	// A repo should not change models mid-life like this.
	baseRepo.SetEntityFactory(func() eh.Entity {
		return &mocks.SimpleModel{}
	})
	// Insert a non-versioned item.
	simpleModel := &mocks.SimpleModel{
		ID:      uuid.New(),
		Content: "simpleModel",
	}
	if err := r.Save(ctx, simpleModel); err != nil {
		t.Error("there should be no error:", err)
	}

	// Find with min version without version.
	ctxVersion := NewContextWithMinVersion(ctx, 1)
	model, err := r.Find(ctxVersion, simpleModel.ID)
	var repoErr eh.RepoError
	if !errors.As(err, &repoErr) || !errors.Is(err, eh.ErrEntityHasNoVersion) {
		t.Error("there should be a model has no version error:", err)
	}

	// Reset back to the model with version.
	baseRepo.SetEntityFactory(func() eh.Entity {
		return &mocks.Model{}
	})
	// Insert a versioned item.
	m1 := &mocks.Model{
		ID:        uuid.New(),
		Version:   1,
		Content:   "m1",
		CreatedAt: time.Now().Round(time.Microsecond).UTC(),
	}
	if err := r.Save(ctx, m1); err != nil {
		t.Error("there should be no error:", err)
	}

	// Find with min version, too low.
	ctxVersion = NewContextWithMinVersion(ctx, 2)
	model, err = r.Find(ctxVersion, m1.ID)
	if !errors.As(err, &repoErr) || !errors.Is(err, eh.ErrIncorrectEntityVersion) {
		t.Error("there should be a incorrect model version error:", err)
	}

	// Find with min version, exactly.
	m1.Version = 2
	if err := r.Save(ctx, m1); err != nil {
		t.Error("there should be no error:", err)
	}
	ctxVersion = NewContextWithMinVersion(ctx, 2)
	model, err = r.Find(ctxVersion, m1.ID)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if !reflect.DeepEqual(model, m1) {
		t.Error("the item should be correct:", model)
	}

	// Find with min version, higher.
	m1.Version = 3
	if err := r.Save(ctx, m1); err != nil {
		t.Error("there should be no error:", err)
	}
	ctxVersion = NewContextWithMinVersion(ctx, 2)
	model, err = r.Find(ctxVersion, m1.ID)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if !reflect.DeepEqual(model, m1) {
		t.Error("the item should be correct:", model)
	}

	// Find with min version, with timeout, data available immediately.
	m1.Version = 4
	if err := r.Save(ctx, m1); err != nil {
		t.Error("there should be no error:", err)
	}
	ctxVersion = NewContextWithMinVersion(ctx, 4)
	var cancel func()
	ctxVersion, cancel = context.WithTimeout(ctxVersion, time.Second)
	defer cancel()
	model, err = r.Find(ctxVersion, m1.ID)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if !reflect.DeepEqual(model, m1) {
		t.Error("the item should be correct:", model)
	}

	// Find with min version, with timeout, data available on retry.
	go func() {
		<-time.After(100 * time.Millisecond)
		m1.Version = 5
		if err := r.Save(ctx, m1); err != nil {
			t.Error("there should be no error:", err)
		}
	}()
	ctxVersion = NewContextWithMinVersion(ctx, 5)
	ctxVersion, cancel = context.WithTimeout(ctxVersion, time.Second)
	defer cancel()
	model, err = r.Find(ctxVersion, m1.ID)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if !reflect.DeepEqual(model, m1) {
		t.Error("the item should be correct:", model)
	}

	// Find with min version, with timeout, data available immediately.
	m2 := &mocks.Model{
		ID:        uuid.New(),
		Version:   1,
		Content:   "m2",
		CreatedAt: time.Now().Round(time.Microsecond).UTC(),
	}
	if err := r.Save(ctx, m2); err != nil {
		t.Error("there should be no error:", err)
	}
	ctxVersion = NewContextWithMinVersion(ctx, 1)
	ctxVersion, cancel = context.WithTimeout(ctxVersion, time.Second)
	defer cancel()
	// Meassure the time it takes, it should not wait > 100ms (the first retry).
	t1 := time.Now()
	model, err = r.Find(ctxVersion, m2.ID)
	dt := time.Now().Sub(t1)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if dt > 10*time.Millisecond {
		t.Error("the result should be available without delay")
	}
	if !reflect.DeepEqual(model, m2) {
		t.Error("the item should be correct:", model)
	}

	// Find with min version, with timeout, created data available on retry.
	m3 := &mocks.Model{
		ID:        uuid.New(),
		Version:   1,
		Content:   "m3",
		CreatedAt: time.Now().Round(time.Microsecond).UTC(),
	}
	go func() {
		<-time.After(100 * time.Millisecond)
		if err := r.Save(ctx, m3); err != nil {
			t.Error("there should be no error:", err)
		}
	}()
	ctxVersion = NewContextWithMinVersion(ctx, 1)
	ctxVersion, cancel = context.WithTimeout(ctxVersion, time.Second)
	defer cancel()
	model, err = r.Find(ctxVersion, m3.ID)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if !reflect.DeepEqual(model, m3) {
		t.Error("the item should be correct:", model)
	}

	// Find with min version, with timeout exceeded.
	go func() {
		<-time.After(100 * time.Millisecond)
		m3.Version = 6
		if err := r.Save(ctx, m3); err != nil {
			t.Error("there should be no error:", err)
		}
	}()
	ctxVersion = NewContextWithMinVersion(ctx, 6)
	ctxVersion, cancel = context.WithTimeout(ctxVersion, 10*time.Millisecond)
	defer cancel()
	model, err = r.Find(ctxVersion, m3.ID)
	if !errors.Is(err, context.DeadlineExceeded) {
		t.Error("there should be a deadline exceeded error:", err)
	}

	// Find with min version, with timeout, created data available on retry.
	m4 := &mocks.Model{
		ID:        uuid.New(),
		Version:   4,
		Content:   "modelMinVersion",
		CreatedAt: time.Now().Round(time.Microsecond).UTC(),
	}
	go func() {
		<-time.After(100 * time.Millisecond)
		if err := r.Save(ctx, m4); err != nil {
			t.Error("there should be no error:", err)
		}
	}()
	ctxVersion = NewContextWithMinVersion(ctx, 4)
	ctxVersion, cancel = context.WithTimeout(ctxVersion, time.Second)
	defer cancel()
	model, err = r.Find(ctxVersion, m4.ID)
	if err != nil {
		t.Error("there should be no error:", err)
	}
	if !reflect.DeepEqual(model, m4) {
		t.Error("the item should be correct:", model)
	}
}

func TestRepository(t *testing.T) {
	if r := Repository(nil); r != nil {
		t.Error("the parent repository should be nil:", r)
	}

	inner := &mocks.Repo{}
	if r := Repository(inner); r != nil {
		t.Error("the parent repository should be nil:", r)
	}

	repo := NewRepo(inner)
	outer := &mocks.Repo{ParentRepo: repo}
	if r := Repository(outer); r != repo {
		t.Error("the parent repository should be correct:", r)
	}
}
<|endoftext|>


<commit_before>// Copyright 2020 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

package executor

import (
	"bytes"
	"io/ioutil"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
	"testing"
)

func TestExecutorMistakes(t *testing.T) {
	checks := []*struct {
		pattern     string
		suppression string
		message     string
		tests       []string
		commonOnly  bool
	}{
		{
			pattern:    `\)\n\t*(debug|debug_dump_data)\(`,
			message:    "debug() calls are stripped from C reproducers, this code will break. Use {} around debug() to fix",
			commonOnly: true,
			tests: []string{
				`
if (foo)
	debug("foo failed");
`, `
	if (x + y)
		debug_dump_data(data, len);
`,
			},
		},
		{
			pattern:     `\) {\n[^\n}]+?\n\t*}\n`,
			suppression: "debug|__except",
			message:     "Don't use single-line compound statements (remove {})",
			tests: []string{
				`
if (foo) {
	bar();
}
`, `
	while (x + y) {
		foo(a, y);
	}
`,
			},
		},
		{
			// These are also not properly stripped by pkg/csource.
			pattern: `/\*[^{]`,
			message: "Don't use /* */ block comments. Use // line comments instead",
			tests: []string{
				`/* C++ comment */`,
			},
		},
		{
			pattern: `#define __NR_`,
			message: "Don't define syscall __NR_foo constants.\n" +
				"These should be guarded by #ifndef __NR_foo, but this is dependent on the host " +
				"and may break on other machines (after pkg/csource processing).\n" +
				"Define sys_foo constants instead.",
			tests: []string{
				`
#ifndef __NR_io_uring_setup
#ifdef __alpha__
#define __NR_io_uring_setup 535
#else // !__alpha__
#define __NR_io_uring_setup 425
#endif
#endif // __NR_io_uring_setup
`,
			},
		},
		{
			pattern:     `//[^\s]`,
			suppression: `https?://`,
			message:     "Add a space after //",
			tests: []string{
				`//foo`,
			},
		},
		{
			// This detects C89-style variable declarations in the beginning of block in a best-effort manner.
			// Struct fields look exactly as C89 variable declarations, to filter them out we look for "{"
			// at the beginning of the line.
			pattern: `
{[^{]*
\s+((unsigned )?[a-zA-Z][a-zA-Z0-9_]+\s*\*?|(struct )?[a-zA-Z][a-zA-Z0-9_]+\*)\s+([a-zA-Z][a-zA-Z0-9_]*(,\s*)?)+;
`,
			suppression: `return |goto |va_list |pthread_|zx_`,
			message:     "Don't use C89 var declarations. Declare vars where they are needed and combine with initialization",
			tests: []string{
				`
{
	int i;
`,
				`
{
	socklen_t optlen;
`,
				`
{
	int fd, rv;
`,
				`
{
	int fd, rv;
`,
				`
{
	struct nlattr* attr;
`,
				`
{
	int* i;
`,
				`
{
	DIR* dp;
`,
			},
		},
		{
			pattern: `(fail|exitf)\(".*\\n`,
			message: "Don't use \\n in fail/exitf messages",
			tests: []string{
				`fail("some message with new line\n");`,
			},
		},
		{
			pattern: `fail(msg)?\("[^"]*%`,
			message: "DON'T",
			tests: []string{
				`fail("format %s string")`,
				`failmsg("format %s string", "format")`,
			},
		},
	}
	for _, check := range checks {
		re := regexp.MustCompile(check.pattern)
		for _, test := range check.tests {
			if !re.MatchString(test) {
				t.Fatalf("patter %q does not match test %q", check.pattern, test)
			}
		}
	}
	for _, file := range executorFiles(t) {
		data, err := ioutil.ReadFile(file)
		if err != nil {
			t.Fatal(err)
		}
		for _, check := range checks {
			if check.commonOnly && !strings.Contains(file, "common") {
				continue
			}
			re := regexp.MustCompile(check.pattern)
			supp := regexp.MustCompile(check.suppression)
			for _, match := range re.FindAllIndex(data, -1) {
				end := match[1] - 1
				for end != len(data) && data[end] != '\n' {
					end++
				}
				// Match suppressions against all lines of the match.
				start := match[0] - 1
				for start != 0 && data[start-1] != '\n' {
					start--
				}
				if check.suppression != "" && supp.Match(data[start:end]) {
					continue
				}
				// Locate the last line of the match, that's where we assume the error is.
				start = end - 1
				for start != 0 && data[start-1] != '\n' {
					start--
				}

				line := bytes.Count(data[:start], []byte{'\n'}) + 1
				t.Errorf("\nexecutor/%v:%v: %v\n%s\n", file, line, check.message, data[start:end])
			}
		}
	}
}

func executorFiles(t *testing.T) []string {
	cc, err := filepath.Glob("*.cc")
	if err != nil {
		t.Fatal(err)
	}
	h, err := filepath.Glob("*.h")
	if err != nil {
		t.Fatal(err)
	}
	if len(cc) == 0 || len(h) == 0 {
		t.Fatal("found no executor files")
	}
	res := append(cc, h...)
	sort.Strings(res)
	return res
}
<commit_msg>executor: add an ifdef SYZ_* style check<commit_after>// Copyright 2020 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

package executor

import (
	"bytes"
	"io/ioutil"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
	"testing"
)

func TestExecutorMistakes(t *testing.T) {
	checks := []*struct {
		pattern     string
		suppression string
		message     string
		tests       []string
		commonOnly  bool
	}{
		{
			pattern:    `\)\n\t*(debug|debug_dump_data)\(`,
			message:    "debug() calls are stripped from C reproducers, this code will break. Use {} around debug() to fix",
			commonOnly: true,
			tests: []string{
				`
if (foo)
	debug("foo failed");
`, `
	if (x + y)
		debug_dump_data(data, len);
`,
			},
		},
		{
			pattern:     `\) {\n[^\n}]+?\n\t*}\n`,
			suppression: "debug|__except",
			message:     "Don't use single-line compound statements (remove {})",
			tests: []string{
				`
if (foo) {
	bar();
}
`, `
	while (x + y) {
		foo(a, y);
	}
`,
			},
		},
		{
			// These are also not properly stripped by pkg/csource.
			pattern: `/\*[^{]`,
			message: "Don't use /* */ block comments. Use // line comments instead",
			tests: []string{
				`/* C++ comment */`,
			},
		},
		{
			pattern: `#define __NR_`,
			message: "Don't define syscall __NR_foo constants.\n" +
				"These should be guarded by #ifndef __NR_foo, but this is dependent on the host " +
				"and may break on other machines (after pkg/csource processing).\n" +
				"Define sys_foo constants instead.",
			tests: []string{
				`
#ifndef __NR_io_uring_setup
#ifdef __alpha__
#define __NR_io_uring_setup 535
#else // !__alpha__
#define __NR_io_uring_setup 425
#endif
#endif // __NR_io_uring_setup
`,
			},
		},
		{
			pattern:     `//[^\s]`,
			suppression: `https?://`,
			message:     "Add a space after //",
			tests: []string{
				`//foo`,
			},
		},
		{
			// This detects C89-style variable declarations in the beginning of block in a best-effort manner.
			// Struct fields look exactly as C89 variable declarations, to filter them out we look for "{"
			// at the beginning of the line.
			pattern: `
{[^{]*
\s+((unsigned )?[a-zA-Z][a-zA-Z0-9_]+\s*\*?|(struct )?[a-zA-Z][a-zA-Z0-9_]+\*)\s+([a-zA-Z][a-zA-Z0-9_]*(,\s*)?)+;
`,
			suppression: `return |goto |va_list |pthread_|zx_`,
			message:     "Don't use C89 var declarations. Declare vars where they are needed and combine with initialization",
			tests: []string{
				`
{
	int i;
`,
				`
{
	socklen_t optlen;
`,
				`
{
	int fd, rv;
`,
				`
{
	int fd, rv;
`,
				`
{
	struct nlattr* attr;
`,
				`
{
	int* i;
`,
				`
{
	DIR* dp;
`,
			},
		},
		{
			pattern: `(fail|exitf)\(".*\\n`,
			message: "Don't use \\n in fail/exitf messages",
			tests: []string{
				`fail("some message with new line\n");`,
			},
		},
		{
			pattern: `fail(msg)?\("[^"]*%`,
			message: "DON'T",
			tests: []string{
				`fail("format %s string")`,
				`failmsg("format %s string", "format")`,
			},
		},
		{
			pattern: `ifn?def\s+SYZ_`,
			message: "SYZ_* are always defined, use #if instead of #ifdef",
			tests: []string{
				`#ifndef SYZ_EXECUTOR_USES_FORK_SERVER`,
				`#ifdef SYZ_EXECUTOR_USES_FORK_SERVER`,
			},
		},
	}
	for _, check := range checks {
		re := regexp.MustCompile(check.pattern)
		for _, test := range check.tests {
			if !re.MatchString(test) {
				t.Fatalf("patter %q does not match test %q", check.pattern, test)
			}
		}
	}
	for _, file := range executorFiles(t) {
		data, err := ioutil.ReadFile(file)
		if err != nil {
			t.Fatal(err)
		}
		for _, check := range checks {
			if check.commonOnly && !strings.Contains(file, "common") {
				continue
			}
			re := regexp.MustCompile(check.pattern)
			supp := regexp.MustCompile(check.suppression)
			for _, match := range re.FindAllIndex(data, -1) {
				end := match[1] - 1
				for end != len(data) && data[end] != '\n' {
					end++
				}
				// Match suppressions against all lines of the match.
				start := match[0] - 1
				for start != 0 && data[start-1] != '\n' {
					start--
				}
				if check.suppression != "" && supp.Match(data[start:end]) {
					continue
				}
				// Locate the last line of the match, that's where we assume the error is.
				start = end - 1
				for start != 0 && data[start-1] != '\n' {
					start--
				}

				line := bytes.Count(data[:start], []byte{'\n'}) + 1
				t.Errorf("\nexecutor/%v:%v: %v\n%s\n", file, line, check.message, data[start:end])
			}
		}
	}
}

func executorFiles(t *testing.T) []string {
	cc, err := filepath.Glob("*.cc")
	if err != nil {
		t.Fatal(err)
	}
	h, err := filepath.Glob("*.h")
	if err != nil {
		t.Fatal(err)
	}
	if len(cc) == 0 || len(h) == 0 {
		t.Fatal("found no executor files")
	}
	res := append(cc, h...)
	sort.Strings(res)
	return res
}
<|endoftext|>


<commit_before>package tests

import (
	"bytes"
	"context"
	"testing"

	"go.polydawn.net/go-timeless-api"
	"go.polydawn.net/go-timeless-api/repeatr"
	. "go.polydawn.net/repeatr/testutil"
)

func shouldRun(t *testing.T, runTool repeatr.RunFunc, frm api.Formula, frmCtx api.FormulaContext) (api.RunRecord, string) {
	rr, txt, err := run(t, runTool, frm, baseFormulaCtx)
	AssertNoError(t, err)
	return *rr, txt
}
func run(t *testing.T, runTool repeatr.RunFunc, frm api.Formula, frmCtx api.FormulaContext) (*api.RunRecord, string, error) {
	bm := bufferingMonitor{}.init()
	rr, err := runTool(context.Background(), frm, baseFormulaCtx, repeatr.InputControl{}, bm.monitor())
	return rr, bm.Txt.String(), err
}

type bufferingMonitor struct {
	Txt bytes.Buffer
	Ch  chan repeatr.Event
	Err error
}

func (bm bufferingMonitor) init() *bufferingMonitor {
	bm = bufferingMonitor{
		Ch: make(chan repeatr.Event),
	}
	go func() { // leaks.  fuck the police.
		for {
			bm.Err = repeatr.CopyOut(<-bm.Ch, &bm.Txt)
		}
	}()
	return &bm
}
func (bm *bufferingMonitor) monitor() repeatr.Monitor {
	return repeatr.Monitor{Chan: bm.Ch}
}
<commit_msg>executor: fix race in test output buffering.<commit_after>package tests

import (
	"bytes"
	"context"
	"sync"
	"testing"

	"go.polydawn.net/go-timeless-api"
	"go.polydawn.net/go-timeless-api/repeatr"
	. "go.polydawn.net/repeatr/testutil"
)

func shouldRun(t *testing.T, runTool repeatr.RunFunc, frm api.Formula, frmCtx api.FormulaContext) (api.RunRecord, string) {
	rr, txt, err := run(t, runTool, frm, baseFormulaCtx)
	AssertNoError(t, err)
	return *rr, txt
}
func run(t *testing.T, runTool repeatr.RunFunc, frm api.Formula, frmCtx api.FormulaContext) (*api.RunRecord, string, error) {
	bm := bufferingMonitor{}
	rr, err := runTool(context.Background(), frm, baseFormulaCtx, repeatr.InputControl{}, bm.monitor())
	bm.await()
	return rr, bm.Txt.String(), err
}

type bufferingMonitor struct {
	Ch  chan repeatr.Event
	Wg  sync.WaitGroup
	Txt bytes.Buffer
	Err error
}

func (bm *bufferingMonitor) monitor() repeatr.Monitor {
	*bm = bufferingMonitor{
		Ch: make(chan repeatr.Event),
	}
	bm.Wg.Add(1)
	go func() {
		defer bm.Wg.Done()
		for msg := range bm.Ch {
			bm.Err = repeatr.CopyOut(msg, &bm.Txt)
		}
	}()
	return repeatr.Monitor{Chan: bm.Ch}
}

func (bm *bufferingMonitor) await() error {
	bm.Wg.Wait()
	return bm.Err
}
<|endoftext|>


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

package sdb

import (
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
	"testing"
)

func TestSelect(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

type SelectTest struct {
	domainTest

	query string
	constistentRead bool
	nextToken []byte

	attrMap map[ItemName][]Attribute
	tok []byte
	err error
}

func (t *SelectTest) SetUp(i *TestInfo) {
	// Call common setup code.
	t.domainTest.SetUp(i)
}

func (t *SelectTest) callDomain() {
	t.attrMap, t.tok, t.err = t.domain.Select(t.query, t.constistentRead, t.nextToken)
}

func init() { RegisterTestSuite(&SelectTest{}) }

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *SelectTest) NoExtraOptions() {
	t.query = "taco"

	// Call
	t.callDomain()
	AssertNe(nil, t.c.req)

	AssertThat(
		getSortedKeys(t.c.req),
		ElementsAre(
			"SelectExpression",
		),
	)

	ExpectEq("taco", t.c.req["SelectExpression"])
}

func (t *SelectTest) ConistentRead() {
	ExpectEq("TODO", "")
}

func (t *SelectTest) TokenPresent() {
	ExpectEq("TODO", "")
}

func (t *SelectTest) ConnReturnsError() {
	ExpectEq("TODO", "")
}

func (t *SelectTest) ConnReturnsJunk() {
	ExpectEq("TODO", "")
}

func (t *SelectTest) NoItemsInResponse() {
	ExpectEq("TODO", "")
}

func (t *SelectTest) SomeItemsInResponse() {
	ExpectEq("TODO", "")
}

func (t *SelectTest) NoNextTokenInResponse() {
	ExpectEq("TODO", "")
}

func (t *SelectTest) NextTokenInResponse() {
	ExpectEq("TODO", "")
}
<commit_msg>SelectTest.TokenPresent<commit_after>// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package sdb

import (
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
	"testing"
)

func TestSelect(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

type SelectTest struct {
	domainTest

	query string
	constistentRead bool
	nextToken []byte

	attrMap map[ItemName][]Attribute
	tok []byte
	err error
}

func (t *SelectTest) SetUp(i *TestInfo) {
	// Call common setup code.
	t.domainTest.SetUp(i)
}

func (t *SelectTest) callDomain() {
	t.attrMap, t.tok, t.err = t.domain.Select(t.query, t.constistentRead, t.nextToken)
}

func init() { RegisterTestSuite(&SelectTest{}) }

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *SelectTest) NoExtraOptions() {
	t.query = "taco"

	// Call
	t.callDomain()
	AssertNe(nil, t.c.req)

	AssertThat(
		getSortedKeys(t.c.req),
		ElementsAre(
			"SelectExpression",
		),
	)

	ExpectEq("taco", t.c.req["SelectExpression"])
}

func (t *SelectTest) ConistentRead() {
	t.constistentRead = true

	// Call
	t.callDomain()
	AssertNe(nil, t.c.req)

	ExpectEq("true", t.c.req["ConsistentRead"])
}

func (t *SelectTest) TokenPresent() {
	t.nextToken = []byte("taco")

	// Call
	t.callDomain()
	AssertNe(nil, t.c.req)

	ExpectEq("taco", t.c.req["NextToken"])
}

func (t *SelectTest) ConnReturnsError() {
	ExpectEq("TODO", "")
}

func (t *SelectTest) ConnReturnsJunk() {
	ExpectEq("TODO", "")
}

func (t *SelectTest) NoItemsInResponse() {
	ExpectEq("TODO", "")
}

func (t *SelectTest) SomeItemsInResponse() {
	ExpectEq("TODO", "")
}

func (t *SelectTest) NoNextTokenInResponse() {
	ExpectEq("TODO", "")
}

func (t *SelectTest) NextTokenInResponse() {
	ExpectEq("TODO", "")
}
<|endoftext|>