<commit_before>package websession

import (
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"log"
	"mime"
	"net/http"
	"net/url"
	"strings"

	"zenhack.net/go/sandstorm/capnp/websession"
	"zenhack.net/go/sandstorm/exp/util/bytestream"
	"zenhack.net/go/sandstorm/exp/util/handle"
	"zenhack.net/go/sandstorm/internal/errors"
)

var specialRequestHeaders = map[string]struct{}{
	"Accept":          {},
	"Accept-Encoding": {},
	"Cookie":          {},
	"If-Match":        {},
	"If-None-Match":   {},
}

// Parameters common to all websession request methods
type commonParams interface {
	Path() (string, error)
	Context() (websession.WebSession_Context, error)
}

// Our UiSession implementation; this implements WebSession, and holds both the
// SessionData from the new*Session call and the http.Handler to invoke.
type handlerWebSession struct {
	sessionData SessionData
	handler     http.Handler
}

//// Helpers for common parts of request handling ////

// Intialize a request with the data common to all webession request methods.
//
// The request will have a Context that is derived from ctx, but includes the
// SessionData in its Values.
func (h *handlerWebSession) initRequest(ctx context.Context, params commonParams) (*basicResponseWriter, *http.Request, error) {
	ctx, cancel := handle.WithCancel(ctx)
	path, err := params.Path()
	if err != nil {
		return nil, nil, err
	}

	wsCtx, err := params.Context()
	if err != nil {
		return nil, nil, err
	}

	// Sandstorm gives us a path no leading slash, but Go's http library
	// expects one:
	parsedUrl, err := url.ParseRequestURI("/" + path)
	if err != nil {
		return nil, nil, err
	}

	ctx = context.WithValue(ctx, sessionDataKey, h.sessionData)
	req := &http.Request{
		Header: http.Header{},
		URL:    parsedUrl,
	}

	req = req.WithContext(ctx)
	err = copyContextInfo(req, wsCtx)
	if err != nil {
		return nil, nil, err
	}

	// Set this as a default, so it's never nil.
	req.Body = http.NoBody

	w := &basicResponseWriter{
		statusCode:     0,
		header:         http.Header{},
		cancel:         cancel,
		responseStream: wsCtx.ResponseStream(),
	}
	w.bodyWriter = bytestream.ToWriteCloser(req.Context(), w.responseStream)

	return w, req, nil
}

// Copy the information from the context into the request.
func copyContextInfo(req *http.Request, wsCtx websession.WebSession_Context) error {
	// cookies

	cookies, err := wsCtx.Cookies()
	if err != nil {
		return err
	}
	numCookies := cookies.Len()
	for i := 0; i < numCookies; i++ {
		kv := cookies.At(i)
		key, err := kv.Key()
		if err != nil {
			return err
		}
		val, err := kv.Value()
		if err != nil {
			return err
		}
		req.AddCookie(&http.Cookie{
			Name:  key,
			Value: val,
		})
	}

	// accept

	accept, err := wsCtx.Accept()
	if err != nil {
		return err
	}
	acceptHeaders := make([]string, accept.Len())
	for i := range acceptHeaders {
		str, err := formatAccept(accept.At(i))
		if err != nil {
			return err
		}
		acceptHeaders[i] = str
	}
	req.Header["Accept"] = acceptHeaders

	acceptEncoding, err := wsCtx.AcceptEncoding()
	if err != nil {
		return err
	}
	acceptEncodingHeaders := make([]string, acceptEncoding.Len())
	for i := range acceptEncodingHeaders {
		encoding, err := acceptEncoding.At(i).ContentCoding()
		if err != nil {
			return err
		}
		acceptEncodingHeaders[i] = fmt.Sprintf(
			"%s;q=%v",
			encoding,
			acceptEncoding.At(i).QValue())
	}
	req.Header["Accept-Encoding"] = acceptEncodingHeaders

	eTagPrecondition := wsCtx.ETagPrecondition()
	switch eTagPrecondition.Which() {
	case websession.WebSession_Context_eTagPrecondition_Which_none:
	case websession.WebSession_Context_eTagPrecondition_Which_exists:
		req.Header.Set("If-Match", "*")
	case websession.WebSession_Context_eTagPrecondition_Which_doesntExist:
		req.Header.Set("If-None-Match", "*")
	case websession.WebSession_Context_eTagPrecondition_Which_matchesOneOf:
		etags, err := eTagPrecondition.MatchesOneOf()
		if err != nil {
			return err
		}
		etagString, err := formatETags(etags)
		if err != nil {
			return err
		}
		req.Header.Set("If-Match", etagString)
	case websession.WebSession_Context_eTagPrecondition_Which_matchesNoneOf:
		etags, err := eTagPrecondition.MatchesNoneOf()
		if err != nil {
			return err
		}
		etagString, err := formatETags(etags)
		if err != nil {
			return err
		}
		req.Header.Set("If-None-Match", etagString)
	}

	additionalHeaders, err := wsCtx.AdditionalHeaders()
	if err != nil {
		return err
	}
	for i := 0; i < additionalHeaders.Len(); i++ {
		hdr := additionalHeaders.At(i)
		name, err := hdr.Name()
		if err != nil {
			return err
		}
		value, err := hdr.Value()
		if err != nil {
			return err
		}
		_, ok := specialRequestHeaders[name]
		if ok {
			log.Printf("Warning: special request header %q in "+
				"websession additionalHeaders field", name)
		}
		req.Header.Set(name, value)
	}

	return nil
}

// format the argument as expected for the value of the "Accept" header.
func formatAccept(typ websession.WebSession_AcceptedType) (string, error) {
	mimeType, err := typ.MimeType()
	if err != nil {
		return "", err
	}
	param := map[string]string{
		"q": fmt.Sprint(typ.QValue()),
	}
	return mime.FormatMediaType(mimeType, param), nil
}

func formatETags(etags websession.WebSession_ETag_List) (string, error) {
	etagStrings := make([]string, etags.Len())
	for i := range etagStrings {
		etag := etags.At(i)
		value, err := etag.Value()
		if err != nil {
			return "", err
		}

		// Sandstorm strips off the quotes, so we have to add them back:
		etagStrings[i] = `"` + value + `"`

		if etag.Weak() {
			etagStrings[i] = "W/" + etagStrings[i]
		}
	}
	return strings.Join(etagStrings, ", "), nil
}

// Common logic for the request methods that return a websession.WebSession_Request.
//
// `ctx` should be the context from the capnp method argument.
//
// `params` should be the capnp Params for the method.
//
// `response` should be the Response object to store the result in.
//
// `customize` is a function which will be called just before ServeHTTP. It
// is responsible for setting the HTTP Method on the request, as well as any
// other capnp method specific data.
func (h *handlerWebSession) handleCommon(
	ctx context.Context,
	params commonParams,
	response websession.WebSession_Response,
	customize func(*http.Request) error,
) error {
	w, req, err := h.initRequest(ctx, params)
	if err != nil {
		return err
	}
	w.response = response

	err = customize(req)
	if err != nil {
		return err
	}

	h.handler.ServeHTTP(w, req)
	if w.statusCode == 0 {
		w.WriteHeader(200)
	}
	return w.finishResponse(req.Context())
}

// PostContent/PutContent
type pContent interface {
	MimeType() (string, error)
	Content() ([]byte, error)
	HasEncoding() bool
	Encoding() (string, error)
}

func copyPContent(req *http.Request, content pContent) error {
	mimeType, err := content.MimeType()
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", mimeType)

	data, err := content.Content()
	if err != nil {
		return err
	}
	req.Body = ioutil.NopCloser(bytes.NewBuffer(data))

	if content.HasEncoding() {
		encoding, err := content.Encoding()
		if err != nil {
			return err
		}
		req.Header.Set("Content-Encoding", encoding)
	}
	return nil
}

// Common logic for capnp methods which take a `pContent` argument.
//
// `ctx`, `params`, and `response` are the same as in `handleCommon`.
//
// `content` is the `content` parameter.
// `method` is the HTTP method to set.
func (h *handlerWebSession) handlePContent(
	ctx context.Context,
	params commonParams,
	response websession.WebSession_Response,
	content pContent,
	method string,
) error {
	return h.handleCommon(ctx, params, response, func(req *http.Request) error {
		err := copyPContent(req, content)
		if err != nil {
			return err
		}
		req.Method = method
		return nil
	})
}

//// Actual WebSession methods ////

func (h *handlerWebSession) Get(p websession.WebSession_get) error {
	return h.handleCommon(p.Ctx, p.Params, p.Results, func(req *http.Request) error {
		if p.Params.IgnoreBody() {
			req.Method = "HEAD"
		} else {
			req.Method = "GET"
		}
		return nil
	})
}

func (h *handlerWebSession) Post(p websession.WebSession_post) error {
	content, err := p.Params.Content()
	if err != nil {
		return err
	}
	return h.handlePContent(p.Ctx, p.Params, p.Results, content, "POST")
}

func (h *handlerWebSession) Put(p websession.WebSession_put) error {
	content, err := p.Params.Content()
	if err != nil {
		return err
	}
	return h.handlePContent(p.Ctx, p.Params, p.Results, content, "PUT")
}

func (h *handlerWebSession) Delete(p websession.WebSession_delete) error {
	return h.handleCommon(p.Ctx, p.Params, p.Results, func(req *http.Request) error {
		req.Method = "DELETE"
		return nil
	})
}

func (h *handlerWebSession) Patch(p websession.WebSession_patch) error {
	content, err := p.Params.Content()
	if err != nil {
		return err
	}
	return h.handlePContent(p.Ctx, p.Params, p.Results, content, "PATCH")
}
func (h *handlerWebSession) PostStreaming(p websession.WebSession_postStreaming) error {
	// TODO: copy mimeType & encoding.
	reqR, reqW := bytestream.PipeServer()
	reqStream := &requestStream{
		ByteStream_Server: reqW,
		responseChan:      make(chan websession.WebSession_Response, 1),
		errChan:           make(chan error, 1),
	}
	p.Results.SetStream(websession.WebSession_RequestStream_ServerToClient(reqStream))
	response, err := websession.NewWebSession_Response(p.Params.Segment())
	if err != nil {
		panic("Error allocating response: " + err.Error())
	}
	go func() {
		// It's not clear to me(zenhack) what context we should use here;
		// we can't use p.Ctx because that will be canceled when
		// postStreaming returns.
		basicW, req, err := h.initRequest(context.TODO(), p.Params)
		if err != nil {
			reqStream.errChan <- err
			return
		}
		basicW.response = response
		w := &streamingResponseWriter{
			basic:        basicW,
			responseChan: reqStream.responseChan,
		}
		req.Body = reqR
		h.handler.ServeHTTP(w, req)
		if w.basic.statusCode == 0 {
			w.WriteHeader(200)
		}
		w.basic.finishResponse(req.Context())
	}()
	return nil
}

//// Stubs for unimplemented WebSession methods ////

func (*handlerWebSession) PutStreaming(p websession.WebSession_putStreaming) error {
	return errors.UnImplementedExn(p.Results.Segment())
}

func (*handlerWebSession) OpenWebSocket(p websession.WebSession_openWebSocket) error {
	return errors.UnImplementedExn(p.Results.Segment())
}

func (*handlerWebSession) Propfind(p websession.WebSession_propfind) error {
	return errors.UnImplementedExn(p.Results.Segment())
}

func (*handlerWebSession) Proppatch(p websession.WebSession_proppatch) error {
	return errors.UnImplementedExn(p.Results.Segment())
}

func (*handlerWebSession) Mkcol(p websession.WebSession_mkcol) error {
	return errors.UnImplementedExn(p.Results.Segment())
}

func (*handlerWebSession) Copy(p websession.WebSession_copy) error {
	return errors.UnImplementedExn(p.Results.Segment())
}

func (*handlerWebSession) Move(p websession.WebSession_move) error {
	return errors.UnImplementedExn(p.Results.Segment())
}

func (*handlerWebSession) Lock(p websession.WebSession_lock) error {
	return errors.UnImplementedExn(p.Results.Segment())
}

func (*handlerWebSession) Unlock(p websession.WebSession_unlock) error {
	return errors.UnImplementedExn(p.Results.Segment())
}

func (*handlerWebSession) Acl(p websession.WebSession_acl) error {
	return errors.UnImplementedExn(p.Results.Segment())
}

func (*handlerWebSession) Report(p websession.WebSession_report) error {
	return errors.UnImplementedExn(p.Results.Segment())
}

func (h *handlerWebSession) Options(p websession.WebSession_options) error {
	return errors.UnImplementedExn(p.Results.Segment())
}
<commit_msg>postStreaming: set method<commit_after>package websession

import (
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"log"
	"mime"
	"net/http"
	"net/url"
	"strings"

	"zenhack.net/go/sandstorm/capnp/websession"
	"zenhack.net/go/sandstorm/exp/util/bytestream"
	"zenhack.net/go/sandstorm/exp/util/handle"
	"zenhack.net/go/sandstorm/internal/errors"
)

var specialRequestHeaders = map[string]struct{}{
	"Accept":          {},
	"Accept-Encoding": {},
	"Cookie":          {},
	"If-Match":        {},
	"If-None-Match":   {},
}

// Parameters common to all websession request methods
type commonParams interface {
	Path() (string, error)
	Context() (websession.WebSession_Context, error)
}

// Our UiSession implementation; this implements WebSession, and holds both the
// SessionData from the new*Session call and the http.Handler to invoke.
type handlerWebSession struct {
	sessionData SessionData
	handler     http.Handler
}

//// Helpers for common parts of request handling ////

// Intialize a request with the data common to all webession request methods.
//
// The request will have a Context that is derived from ctx, but includes the
// SessionData in its Values.
func (h *handlerWebSession) initRequest(ctx context.Context, params commonParams) (*basicResponseWriter, *http.Request, error) {
	ctx, cancel := handle.WithCancel(ctx)
	path, err := params.Path()
	if err != nil {
		return nil, nil, err
	}

	wsCtx, err := params.Context()
	if err != nil {
		return nil, nil, err
	}

	// Sandstorm gives us a path no leading slash, but Go's http library
	// expects one:
	parsedUrl, err := url.ParseRequestURI("/" + path)
	if err != nil {
		return nil, nil, err
	}

	ctx = context.WithValue(ctx, sessionDataKey, h.sessionData)
	req := &http.Request{
		Header: http.Header{},
		URL:    parsedUrl,
	}

	req = req.WithContext(ctx)
	err = copyContextInfo(req, wsCtx)
	if err != nil {
		return nil, nil, err
	}

	// Set this as a default, so it's never nil.
	req.Body = http.NoBody

	w := &basicResponseWriter{
		statusCode:     0,
		header:         http.Header{},
		cancel:         cancel,
		responseStream: wsCtx.ResponseStream(),
	}
	w.bodyWriter = bytestream.ToWriteCloser(req.Context(), w.responseStream)

	return w, req, nil
}

// Copy the information from the context into the request.
func copyContextInfo(req *http.Request, wsCtx websession.WebSession_Context) error {
	// cookies

	cookies, err := wsCtx.Cookies()
	if err != nil {
		return err
	}
	numCookies := cookies.Len()
	for i := 0; i < numCookies; i++ {
		kv := cookies.At(i)
		key, err := kv.Key()
		if err != nil {
			return err
		}
		val, err := kv.Value()
		if err != nil {
			return err
		}
		req.AddCookie(&http.Cookie{
			Name:  key,
			Value: val,
		})
	}

	// accept

	accept, err := wsCtx.Accept()
	if err != nil {
		return err
	}
	acceptHeaders := make([]string, accept.Len())
	for i := range acceptHeaders {
		str, err := formatAccept(accept.At(i))
		if err != nil {
			return err
		}
		acceptHeaders[i] = str
	}
	req.Header["Accept"] = acceptHeaders

	acceptEncoding, err := wsCtx.AcceptEncoding()
	if err != nil {
		return err
	}
	acceptEncodingHeaders := make([]string, acceptEncoding.Len())
	for i := range acceptEncodingHeaders {
		encoding, err := acceptEncoding.At(i).ContentCoding()
		if err != nil {
			return err
		}
		acceptEncodingHeaders[i] = fmt.Sprintf(
			"%s;q=%v",
			encoding,
			acceptEncoding.At(i).QValue())
	}
	req.Header["Accept-Encoding"] = acceptEncodingHeaders

	eTagPrecondition := wsCtx.ETagPrecondition()
	switch eTagPrecondition.Which() {
	case websession.WebSession_Context_eTagPrecondition_Which_none:
	case websession.WebSession_Context_eTagPrecondition_Which_exists:
		req.Header.Set("If-Match", "*")
	case websession.WebSession_Context_eTagPrecondition_Which_doesntExist:
		req.Header.Set("If-None-Match", "*")
	case websession.WebSession_Context_eTagPrecondition_Which_matchesOneOf:
		etags, err := eTagPrecondition.MatchesOneOf()
		if err != nil {
			return err
		}
		etagString, err := formatETags(etags)
		if err != nil {
			return err
		}
		req.Header.Set("If-Match", etagString)
	case websession.WebSession_Context_eTagPrecondition_Which_matchesNoneOf:
		etags, err := eTagPrecondition.MatchesNoneOf()
		if err != nil {
			return err
		}
		etagString, err := formatETags(etags)
		if err != nil {
			return err
		}
		req.Header.Set("If-None-Match", etagString)
	}

	additionalHeaders, err := wsCtx.AdditionalHeaders()
	if err != nil {
		return err
	}
	for i := 0; i < additionalHeaders.Len(); i++ {
		hdr := additionalHeaders.At(i)
		name, err := hdr.Name()
		if err != nil {
			return err
		}
		value, err := hdr.Value()
		if err != nil {
			return err
		}
		_, ok := specialRequestHeaders[name]
		if ok {
			log.Printf("Warning: special request header %q in "+
				"websession additionalHeaders field", name)
		}
		req.Header.Set(name, value)
	}

	return nil
}

// format the argument as expected for the value of the "Accept" header.
func formatAccept(typ websession.WebSession_AcceptedType) (string, error) {
	mimeType, err := typ.MimeType()
	if err != nil {
		return "", err
	}
	param := map[string]string{
		"q": fmt.Sprint(typ.QValue()),
	}
	return mime.FormatMediaType(mimeType, param), nil
}

func formatETags(etags websession.WebSession_ETag_List) (string, error) {
	etagStrings := make([]string, etags.Len())
	for i := range etagStrings {
		etag := etags.At(i)
		value, err := etag.Value()
		if err != nil {
			return "", err
		}

		// Sandstorm strips off the quotes, so we have to add them back:
		etagStrings[i] = `"` + value + `"`

		if etag.Weak() {
			etagStrings[i] = "W/" + etagStrings[i]
		}
	}
	return strings.Join(etagStrings, ", "), nil
}

// Common logic for the request methods that return a websession.WebSession_Request.
//
// `ctx` should be the context from the capnp method argument.
//
// `params` should be the capnp Params for the method.
//
// `response` should be the Response object to store the result in.
//
// `customize` is a function which will be called just before ServeHTTP. It
// is responsible for setting the HTTP Method on the request, as well as any
// other capnp method specific data.
func (h *handlerWebSession) handleCommon(
	ctx context.Context,
	params commonParams,
	response websession.WebSession_Response,
	customize func(*http.Request) error,
) error {
	w, req, err := h.initRequest(ctx, params)
	if err != nil {
		return err
	}
	w.response = response

	err = customize(req)
	if err != nil {
		return err
	}

	h.handler.ServeHTTP(w, req)
	if w.statusCode == 0 {
		w.WriteHeader(200)
	}
	return w.finishResponse(req.Context())
}

// PostContent/PutContent
type pContent interface {
	MimeType() (string, error)
	Content() ([]byte, error)
	HasEncoding() bool
	Encoding() (string, error)
}

func copyPContent(req *http.Request, content pContent) error {
	mimeType, err := content.MimeType()
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", mimeType)

	data, err := content.Content()
	if err != nil {
		return err
	}
	req.Body = ioutil.NopCloser(bytes.NewBuffer(data))

	if content.HasEncoding() {
		encoding, err := content.Encoding()
		if err != nil {
			return err
		}
		req.Header.Set("Content-Encoding", encoding)
	}
	return nil
}

// Common logic for capnp methods which take a `pContent` argument.
//
// `ctx`, `params`, and `response` are the same as in `handleCommon`.
//
// `content` is the `content` parameter.
// `method` is the HTTP method to set.
func (h *handlerWebSession) handlePContent(
	ctx context.Context,
	params commonParams,
	response websession.WebSession_Response,
	content pContent,
	method string,
) error {
	return h.handleCommon(ctx, params, response, func(req *http.Request) error {
		err := copyPContent(req, content)
		if err != nil {
			return err
		}
		req.Method = method
		return nil
	})
}

//// Actual WebSession methods ////

func (h *handlerWebSession) Get(p websession.WebSession_get) error {
	return h.handleCommon(p.Ctx, p.Params, p.Results, func(req *http.Request) error {
		if p.Params.IgnoreBody() {
			req.Method = "HEAD"
		} else {
			req.Method = "GET"
		}
		return nil
	})
}

func (h *handlerWebSession) Post(p websession.WebSession_post) error {
	content, err := p.Params.Content()
	if err != nil {
		return err
	}
	return h.handlePContent(p.Ctx, p.Params, p.Results, content, "POST")
}

func (h *handlerWebSession) Put(p websession.WebSession_put) error {
	content, err := p.Params.Content()
	if err != nil {
		return err
	}
	return h.handlePContent(p.Ctx, p.Params, p.Results, content, "PUT")
}

func (h *handlerWebSession) Delete(p websession.WebSession_delete) error {
	return h.handleCommon(p.Ctx, p.Params, p.Results, func(req *http.Request) error {
		req.Method = "DELETE"
		return nil
	})
}

func (h *handlerWebSession) Patch(p websession.WebSession_patch) error {
	content, err := p.Params.Content()
	if err != nil {
		return err
	}
	return h.handlePContent(p.Ctx, p.Params, p.Results, content, "PATCH")
}
func (h *handlerWebSession) PostStreaming(p websession.WebSession_postStreaming) error {
	reqR, reqW := bytestream.PipeServer()
	reqStream := &requestStream{
		ByteStream_Server: reqW,
		responseChan:      make(chan websession.WebSession_Response, 1),
		errChan:           make(chan error, 1),
	}
	p.Results.SetStream(websession.WebSession_RequestStream_ServerToClient(reqStream))
	response, err := websession.NewWebSession_Response(p.Params.Segment())
	if err != nil {
		panic("Error allocating response: " + err.Error())
	}
	go func() {
		// It's not clear to me(zenhack) what context we should use here;
		// we can't use p.Ctx because that will be canceled when
		// postStreaming returns.
		basicW, req, err := h.initRequest(context.TODO(), p.Params)
		if err != nil {
			reqStream.errChan <- err
			return
		}

		req.Method = "POST"
		// TODO: copy mimeType & encoding.

		basicW.response = response
		w := &streamingResponseWriter{
			basic:        basicW,
			responseChan: reqStream.responseChan,
		}
		req.Body = reqR
		h.handler.ServeHTTP(w, req)
		if w.basic.statusCode == 0 {
			w.WriteHeader(200)
		}
		w.basic.finishResponse(req.Context())
	}()
	return nil
}

//// Stubs for unimplemented WebSession methods ////

func (*handlerWebSession) PutStreaming(p websession.WebSession_putStreaming) error {
	return errors.UnImplementedExn(p.Results.Segment())
}

func (*handlerWebSession) OpenWebSocket(p websession.WebSession_openWebSocket) error {
	return errors.UnImplementedExn(p.Results.Segment())
}

func (*handlerWebSession) Propfind(p websession.WebSession_propfind) error {
	return errors.UnImplementedExn(p.Results.Segment())
}

func (*handlerWebSession) Proppatch(p websession.WebSession_proppatch) error {
	return errors.UnImplementedExn(p.Results.Segment())
}

func (*handlerWebSession) Mkcol(p websession.WebSession_mkcol) error {
	return errors.UnImplementedExn(p.Results.Segment())
}

func (*handlerWebSession) Copy(p websession.WebSession_copy) error {
	return errors.UnImplementedExn(p.Results.Segment())
}

func (*handlerWebSession) Move(p websession.WebSession_move) error {
	return errors.UnImplementedExn(p.Results.Segment())
}

func (*handlerWebSession) Lock(p websession.WebSession_lock) error {
	return errors.UnImplementedExn(p.Results.Segment())
}

func (*handlerWebSession) Unlock(p websession.WebSession_unlock) error {
	return errors.UnImplementedExn(p.Results.Segment())
}

func (*handlerWebSession) Acl(p websession.WebSession_acl) error {
	return errors.UnImplementedExn(p.Results.Segment())
}

func (*handlerWebSession) Report(p websession.WebSession_report) error {
	return errors.UnImplementedExn(p.Results.Segment())
}

func (h *handlerWebSession) Options(p websession.WebSession_options) error {
	return errors.UnImplementedExn(p.Results.Segment())
}
<|endoftext|>


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

package main

import (
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"reflect"
	"testing"
)

var (
	regen = flag.Bool("regen", false, "regenerate reference files")
)

func TestGenerate(t *testing.T) {
	dir, err := ioutil.TempDir("", "groot-gen-type-")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(dir)

	for _, tc := range []struct {
		fname     string
		want      string
		types     []string
		verbose   bool
		streamers bool
	}{
		{
			fname:     "../../testdata/small-evnt-tree-fullsplit.root",
			want:      "testdata/small-evnt-tree-fullsplit.txt",
			types:     []string{"Event", "P3"},
			streamers: true,
		},
	} {
		t.Run(tc.fname, func(t *testing.T) {
			oname := filepath.Base(tc.fname) + ".go"
			o, err := os.Create(filepath.Join(dir, oname))
			if err != nil {
				t.Fatal(err)
			}
			defer o.Close()

			err = generate(o, "main", tc.types, tc.fname, tc.verbose, tc.streamers)
			if err != nil {
				t.Fatalf("could not generate types: %v", err)
			}

			err = o.Close()
			if err != nil {
				t.Fatal(err)
			}

			got, err := ioutil.ReadFile(o.Name())
			if err != nil {
				t.Fatalf("could not read generated file: %v", err)
			}

			if *regen {
				ioutil.WriteFile(tc.want, got, 0644)
			}

			want, err := ioutil.ReadFile(tc.want)
			if err != nil {
				t.Fatalf("could not read reference file: %v", err)
			}

			if !reflect.DeepEqual(got, want) {
				t.Fatalf("error:\n%v", diff(t, string(got), string(want)))
			}
		})
	}
}

func diff(t *testing.T, chk, ref string) string {
	t.Helper()

	if !hasDiffCmd {
		return fmt.Sprintf("=== got ===\n%s\n=== want ===\n%s\n", chk, ref)
	}

	tmpdir, err := ioutil.TempDir("", "groot-diff-")
	if err != nil {
		t.Fatalf("could not create tmpdir: %v", err)
	}
	defer os.RemoveAll(tmpdir)

	got := filepath.Join(tmpdir, "got.txt")
	err = ioutil.WriteFile(got, []byte(chk), 0644)
	if err != nil {
		t.Fatalf("could not create %s file: %v", got, err)
	}

	want := filepath.Join(tmpdir, "want.txt")
	err = ioutil.WriteFile(want, []byte(ref), 0644)
	if err != nil {
		t.Fatalf("could not create %s file: %v", want, err)
	}

	out := new(bytes.Buffer)
	cmd := exec.Command("diff", "-urN", want, got)
	cmd.Stdout = out
	cmd.Stderr = out
	err = cmd.Run()
	return out.String() + "\nerror: " + err.Error()
}

var hasDiffCmd = false

func init() {
	_, err := exec.LookPath("diff")
	if err == nil {
		hasDiffCmd = true
	}
}
<commit_msg>groot/cmd/root-gen-type: test r/w round-trip<commit_after>// Copyright 2019 The go-hep Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"reflect"
	"testing"
)

var (
	regen = flag.Bool("regen", false, "regenerate reference files")
)

func TestGenerate(t *testing.T) {
	dir, err := ioutil.TempDir("", "groot-gen-type-")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(dir)

	for _, tc := range []struct {
		fname     string
		want      string
		types     []string
		verbose   bool
		streamers bool
	}{
		{
			fname:     "../../testdata/small-evnt-tree-fullsplit.root",
			want:      "testdata/small-evnt-tree-fullsplit.txt",
			types:     []string{"Event", "P3"},
			streamers: true,
		},
	} {
		t.Run(tc.fname, func(t *testing.T) {
			oname := filepath.Base(tc.fname) + ".go"
			o, err := os.Create(filepath.Join(dir, oname))
			if err != nil {
				t.Fatal(err)
			}
			defer o.Close()

			err = generate(o, "main", tc.types, tc.fname, tc.verbose, tc.streamers)
			if err != nil {
				t.Fatalf("could not generate types: %v", err)
			}

			err = o.Close()
			if err != nil {
				t.Fatal(err)
			}

			got, err := ioutil.ReadFile(o.Name())
			if err != nil {
				t.Fatalf("could not read generated file: %v", err)
			}

			if *regen {
				ioutil.WriteFile(tc.want, got, 0644)
			}

			want, err := ioutil.ReadFile(tc.want)
			if err != nil {
				t.Fatalf("could not read reference file: %v", err)
			}

			if !reflect.DeepEqual(got, want) {
				t.Fatalf("error:\n%v", diff(t, string(got), string(want)))
			}
		})
	}
}

func TestRW(t *testing.T) {
	dir, err := ioutil.TempDir("", "groot-gen-type-")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(dir)

	for _, tc := range []struct {
		fname     string
		want      string
		types     []string
		verbose   bool
		streamers bool
		main      string
	}{
		{
			fname:     "../../testdata/small-evnt-tree-fullsplit.root",
			want:      "testdata/small-evnt-tree-fullsplit.txt",
			types:     []string{"Event", "P3"},
			streamers: true,
			main: `
package main

import (
	"flag"
	"log"
	"reflect"

	"go-hep.org/x/hep/groot"
)

func main() {
	{ // FIXME(sbinet): this shouldn't be necessary => bundle streamerinfo
		fname := flag.String("f", "", "file")
		flag.Parse()
		f, err := groot.Open(*fname)
		if err != nil {
			log.Fatal(err)
		}
		f.Close()
	}
	w, err := groot.Create("out.root")
	if err != nil {
		log.Fatal(err)
	}
	defer w.Close()

	wevt := &Event{
		Beg:       "beg",
		I16:       -16,
		I32:       -32,
		I64:       -64,
		U16:       +16,
		U32:       +32,
		U64:       +64,
		F32:       +32,
		F64:       +64,
		Str:       "my-string",
		P3:        P3{1, 2, 3},
		ArrayI16:  [10]int16{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
		ArrayI32:  [10]int32{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
		ArrayI64:  [10]int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
		ArrayU16:  [10]uint16{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
		ArrayU32:  [10]uint32{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
		ArrayU64:  [10]uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
		ArrayF32:  [10]float32{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
		ArrayF64:  [10]float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
		N:         10,
		SliceI16:  []int16{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
		SliceI32:  []int32{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
		SliceI64:  []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
		SliceU16:  []uint16{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
		SliceU32:  []uint32{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
		SliceU64:  []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
		SliceF32:  []float32{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
		SliceF64:  []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
		StdStr:    "std-string",
		StlVecI16: []int16{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
		StlVecI32: []int32{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
		StlVecI64: []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
		StlVecU16: []uint16{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
		StlVecU32: []uint32{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
		StlVecU64: []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
		StlVecF32: []float32{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
		StlVecF64: []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
		StlVecStr: []string{"1", "2", "3", "4", "5", "6", "7", "8", "9"},
		End:       "end",
	}

	err = w.Put("evt", wevt)
	if err != nil {
		log.Fatal(err)
	}

	err = w.Close()
	if err != nil {
		log.Fatalf("error closing out.root file: %v", err)
	}

	r, err := groot.Open("out.root")
	if err != nil {
		log.Fatal(err)
	}
	defer r.Close()

	o, err := r.Get("evt")
	if err != nil {
		log.Fatal(err)
	}

	revt := o.(*Event)
	if !reflect.DeepEqual(revt, wevt) {
		log.Fatalf("error:\ngot= %#v\nwant=%#v", revt, wevt)
	}
}
`,
		},
	} {
		t.Run(tc.fname, func(t *testing.T) {
			oname := filepath.Base(tc.fname) + ".go"
			o, err := os.Create(filepath.Join(dir, oname))
			if err != nil {
				t.Fatal(err)
			}
			defer o.Close()

			err = generate(o, "main", tc.types, tc.fname, tc.verbose, tc.streamers)
			if err != nil {
				t.Fatalf("could not generate types: %v", err)
			}

			err = o.Close()
			if err != nil {
				t.Fatal(err)
			}

			got, err := ioutil.ReadFile(o.Name())
			if err != nil {
				t.Fatalf("could not read generated file: %v", err)
			}

			if *regen {
				ioutil.WriteFile(tc.want, got, 0644)
			}

			want, err := ioutil.ReadFile(tc.want)
			if err != nil {
				t.Fatalf("could not read reference file: %v", err)
			}

			if !reflect.DeepEqual(got, want) {
				t.Fatalf("error:\n%v", diff(t, string(got), string(want)))
			}

			err = ioutil.WriteFile(filepath.Join(dir, "main.go"), []byte(tc.main), 0644)
			if err != nil {
				t.Fatal(err)
			}

			cwd, err := os.Getwd()
			if err != nil {
				t.Fatal(err)
			}

			buf := new(bytes.Buffer)
			cmd := exec.Command("go", "build",
				"-o", filepath.Join(dir, "a.out"),
				filepath.Join(dir, "main.go"),
				filepath.Join(dir, oname),
			)
			cmd.Stdout = buf
			cmd.Stderr = buf
			err = cmd.Run()
			if err != nil {
				t.Fatalf("could not run command %v:\n%v\nerr=%v",
					cmd.Args,
					buf.String(), err)
			}
			buf.Reset()

			cmd = exec.Command("./a.out", "-f", filepath.Join(cwd, tc.fname))
			cmd.Dir = dir
			cmd.Stdout = buf
			cmd.Stderr = buf
			err = cmd.Run()
			if err != nil {
				t.Fatalf("could not run command %v:\n%v\nerr=%v",
					cmd.Args,
					buf.String(), err)
			}
		})
	}
}

func diff(t *testing.T, chk, ref string) string {
	t.Helper()

	if !hasDiffCmd {
		return fmt.Sprintf("=== got ===\n%s\n=== want ===\n%s\n", chk, ref)
	}

	tmpdir, err := ioutil.TempDir("", "groot-diff-")
	if err != nil {
		t.Fatalf("could not create tmpdir: %v", err)
	}
	defer os.RemoveAll(tmpdir)

	got := filepath.Join(tmpdir, "got.txt")
	err = ioutil.WriteFile(got, []byte(chk), 0644)
	if err != nil {
		t.Fatalf("could not create %s file: %v", got, err)
	}

	want := filepath.Join(tmpdir, "want.txt")
	err = ioutil.WriteFile(want, []byte(ref), 0644)
	if err != nil {
		t.Fatalf("could not create %s file: %v", want, err)
	}

	out := new(bytes.Buffer)
	cmd := exec.Command("diff", "-urN", want, got)
	cmd.Stdout = out
	cmd.Stderr = out
	err = cmd.Run()
	return out.String() + "\nerror: " + err.Error()
}

var hasDiffCmd = false

func init() {
	_, err := exec.LookPath("diff")
	if err == nil {
		hasDiffCmd = true
	}
}
<|endoftext|>


<commit_before>// +build linux

package fs2

import (
	"github.com/opencontainers/runc/libcontainer/cgroups/ebpf"
	"github.com/opencontainers/runc/libcontainer/cgroups/ebpf/devicefilter"
	"github.com/opencontainers/runc/libcontainer/configs"
	"github.com/pkg/errors"
	"golang.org/x/sys/unix"
)

func isRWM(cgroupPermissions string) bool {
	r := false
	w := false
	m := false
	for _, rn := range cgroupPermissions {
		switch rn {
		case 'r':
			r = true
		case 'w':
			w = true
		case 'm':
			m = true
		}
	}
	return r && w && m
}

// the logic is from crun
// https://github.com/containers/crun/blob/0.10.2/src/libcrun/cgroup.c#L1644-L1652
func canSkipEBPFError(cgroup *configs.Cgroup) bool {
	for _, dev := range cgroup.Resources.Devices {
		if dev.Allow || !isRWM(dev.Permissions) {
			return false
		}
	}
	return true
}

func setDevices(dirPath string, cgroup *configs.Cgroup) error {
	if cgroup.Resources.AllowAllDevices != nil {
		// never set by OCI specconv
		return errors.New("libcontainer AllowAllDevices is not supported, use Devices")
	}
	if len(cgroup.Resources.DeniedDevices) != 0 {
		// never set by OCI specconv
		return errors.New("libcontainer DeniedDevices is not supported, use Devices")
	}
	insts, license, err := devicefilter.DeviceFilter(cgroup.Devices)
	if err != nil {
		return err
	}
	dirFD, err := unix.Open(dirPath, unix.O_DIRECTORY|unix.O_RDONLY, 0600)
	if err != nil {
		return errors.Errorf("cannot get dir FD for %s", dirPath)
	}
	defer unix.Close(dirFD)
	if _, err := ebpf.LoadAttachCgroupDeviceFilter(insts, license, dirFD); err != nil {
		if !canSkipEBPFError(cgroup) {
			return err
		}
	}
	return nil
}
<commit_msg>fs2: support legacy device spec (to pass CI)<commit_after>// +build linux

package fs2

import (
	"github.com/opencontainers/runc/libcontainer/cgroups/ebpf"
	"github.com/opencontainers/runc/libcontainer/cgroups/ebpf/devicefilter"
	"github.com/opencontainers/runc/libcontainer/configs"
	"github.com/pkg/errors"
	"golang.org/x/sys/unix"
)

func isRWM(cgroupPermissions string) bool {
	r := false
	w := false
	m := false
	for _, rn := range cgroupPermissions {
		switch rn {
		case 'r':
			r = true
		case 'w':
			w = true
		case 'm':
			m = true
		}
	}
	return r && w && m
}

// the logic is from crun
// https://github.com/containers/crun/blob/0.10.2/src/libcrun/cgroup.c#L1644-L1652
func canSkipEBPFError(cgroup *configs.Cgroup) bool {
	for _, dev := range cgroup.Resources.Devices {
		if dev.Allow || !isRWM(dev.Permissions) {
			return false
		}
	}
	return true
}

func setDevices(dirPath string, cgroup *configs.Cgroup) error {
	devices := cgroup.Devices
	if allowAllDevices := cgroup.Resources.AllowAllDevices; allowAllDevices != nil {
		// never set by OCI specconv, but *allowAllDevices=false is still used by the integration test
		if *allowAllDevices == true {
			return errors.New("libcontainer AllowAllDevices is not supported, use Devices")
		}
		for _, ad := range cgroup.Resources.AllowedDevices {
			d := *ad
			d.Allow = true
			devices = append(devices, &d)
		}
	}
	if len(cgroup.Resources.DeniedDevices) != 0 {
		// never set by OCI specconv
		return errors.New("libcontainer DeniedDevices is not supported, use Devices")
	}
	insts, license, err := devicefilter.DeviceFilter(devices)
	if err != nil {
		return err
	}
	dirFD, err := unix.Open(dirPath, unix.O_DIRECTORY|unix.O_RDONLY, 0600)
	if err != nil {
		return errors.Errorf("cannot get dir FD for %s", dirPath)
	}
	defer unix.Close(dirFD)
	if _, err := ebpf.LoadAttachCgroupDeviceFilter(insts, license, dirFD); err != nil {
		if !canSkipEBPFError(cgroup) {
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package webhook

import (
	"k8s.io/api/admissionregistration/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// WebhookAccessor provides a common interface to both mutating and validating webhook types.
type WebhookAccessor interface {
	// GetUID gets a string that uniquely identifies the webhook.
	GetUID() string

	// GetConfigurationName gets the name of the webhook configuration that owns this webhook.
	GetConfigurationName() string

	// GetName gets the webhook Name field. Note that the name is scoped to the webhook
	// configuration and does not provide a globally unique identity, if a unique identity is
	// needed, use GetUID.
	GetName() string
	// GetClientConfig gets the webhook ClientConfig field.
	GetClientConfig() v1beta1.WebhookClientConfig
	// GetRules gets the webhook Rules field.
	GetRules() []v1beta1.RuleWithOperations
	// GetFailurePolicy gets the webhook FailurePolicy field.
	GetFailurePolicy() *v1beta1.FailurePolicyType
	// GetMatchPolicy gets the webhook MatchPolicy field.
	GetMatchPolicy() *v1beta1.MatchPolicyType
	// GetNamespaceSelector gets the webhook NamespaceSelector field.
	GetNamespaceSelector() *metav1.LabelSelector
	// GetObjectSelector gets the webhook ObjectSelector field.
	GetObjectSelector() *metav1.LabelSelector
	// GetSideEffects gets the webhook SideEffects field.
	GetSideEffects() *v1beta1.SideEffectClass
	// GetTimeoutSeconds gets the webhook TimeoutSeconds field.
	GetTimeoutSeconds() *int32
	// GetAdmissionReviewVersions gets the webhook AdmissionReviewVersions field.
	GetAdmissionReviewVersions() []string

	// GetMutatingWebhook if the accessor contains a MutatingWebhook, returns it and true, else returns false.
	GetMutatingWebhook() (*v1beta1.MutatingWebhook, bool)
	// GetValidatingWebhook if the accessor contains a ValidatingWebhook, returns it and true, else returns false.
	GetValidatingWebhook() (*v1beta1.ValidatingWebhook, bool)
}

// NewMutatingWebhookAccessor creates an accessor for a MutatingWebhook.
func NewMutatingWebhookAccessor(uid, configurationName string, h *v1beta1.MutatingWebhook) WebhookAccessor {
	return mutatingWebhookAccessor{uid: uid, configurationName: configurationName, MutatingWebhook: h}
}

type mutatingWebhookAccessor struct {
	*v1beta1.MutatingWebhook
	uid               string
	configurationName string
}

func (m mutatingWebhookAccessor) GetUID() string {
	return m.uid
}

func (m mutatingWebhookAccessor) GetConfigurationName() string {
	return m.configurationName
}

func (m mutatingWebhookAccessor) GetName() string {
	return m.Name
}

func (m mutatingWebhookAccessor) GetClientConfig() v1beta1.WebhookClientConfig {
	return m.ClientConfig
}

func (m mutatingWebhookAccessor) GetRules() []v1beta1.RuleWithOperations {
	return m.Rules
}

func (m mutatingWebhookAccessor) GetFailurePolicy() *v1beta1.FailurePolicyType {
	return m.FailurePolicy
}

func (m mutatingWebhookAccessor) GetMatchPolicy() *v1beta1.MatchPolicyType {
	return m.MatchPolicy
}

func (m mutatingWebhookAccessor) GetNamespaceSelector() *metav1.LabelSelector {
	return m.NamespaceSelector
}

func (m mutatingWebhookAccessor) GetObjectSelector() *metav1.LabelSelector {
	return m.ObjectSelector
}

func (m mutatingWebhookAccessor) GetSideEffects() *v1beta1.SideEffectClass {
	return m.SideEffects
}

func (m mutatingWebhookAccessor) GetTimeoutSeconds() *int32 {
	return m.TimeoutSeconds
}

func (m mutatingWebhookAccessor) GetAdmissionReviewVersions() []string {
	return m.AdmissionReviewVersions
}

func (m mutatingWebhookAccessor) GetMutatingWebhook() (*v1beta1.MutatingWebhook, bool) {
	return m.MutatingWebhook, true
}

func (m mutatingWebhookAccessor) GetValidatingWebhook() (*v1beta1.ValidatingWebhook, bool) {
	return nil, false
}

// NewValidatingWebhookAccessor creates an accessor for a ValidatingWebhook.
func NewValidatingWebhookAccessor(uid, configurationName string, h *v1beta1.ValidatingWebhook) WebhookAccessor {
	return validatingWebhookAccessor{uid: uid, configurationName: configurationName, ValidatingWebhook: h}
}

type validatingWebhookAccessor struct {
	*v1beta1.ValidatingWebhook
	uid               string
	configurationName string
}

func (v validatingWebhookAccessor) GetUID() string {
	return v.uid
}

func (v validatingWebhookAccessor) GetConfigurationName() string {
	return v.configurationName
}

func (v validatingWebhookAccessor) GetName() string {
	return v.Name
}

func (v validatingWebhookAccessor) GetClientConfig() v1beta1.WebhookClientConfig {
	return v.ClientConfig
}

func (v validatingWebhookAccessor) GetRules() []v1beta1.RuleWithOperations {
	return v.Rules
}

func (v validatingWebhookAccessor) GetFailurePolicy() *v1beta1.FailurePolicyType {
	return v.FailurePolicy
}

func (v validatingWebhookAccessor) GetMatchPolicy() *v1beta1.MatchPolicyType {
	return v.MatchPolicy
}

func (v validatingWebhookAccessor) GetNamespaceSelector() *metav1.LabelSelector {
	return v.NamespaceSelector
}

func (v validatingWebhookAccessor) GetObjectSelector() *metav1.LabelSelector {
	return v.ObjectSelector
}

func (v validatingWebhookAccessor) GetSideEffects() *v1beta1.SideEffectClass {
	return v.SideEffects
}

func (v validatingWebhookAccessor) GetTimeoutSeconds() *int32 {
	return v.TimeoutSeconds
}

func (v validatingWebhookAccessor) GetAdmissionReviewVersions() []string {
	return v.AdmissionReviewVersions
}

func (v validatingWebhookAccessor) GetMutatingWebhook() (*v1beta1.MutatingWebhook, bool) {
	return nil, false
}

func (v validatingWebhookAccessor) GetValidatingWebhook() (*v1beta1.ValidatingWebhook, bool) {
	return v.ValidatingWebhook, true
}
<commit_msg>Let webhook accessors construct client/selectors once<commit_after>/*
Copyright 2019 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package webhook

import (
	"sync"
	"fmt"

	"k8s.io/api/admissionregistration/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	webhookutil "k8s.io/apiserver/pkg/util/webhook"
	"k8s.io/client-go/rest"
)

// WebhookAccessor provides a common interface to both mutating and validating webhook types.
type WebhookAccessor interface {
	// GetUID gets a string that uniquely identifies the webhook.
	GetUID() string

	// GetConfigurationName gets the name of the webhook configuration that owns this webhook.
	GetConfigurationName() string

	// GetRESTClient gets the webhook client
	GetRESTClient(clientManager *webhookutil.ClientManager) (*rest.RESTClient, error)
	// GetParsedNamespaceSelector gets the webhook NamespaceSelector field.
	GetParsedNamespaceSelector() (labels.Selector, error)
	// GetParsedObjectSelector gets the webhook ObjectSelector field.
	GetParsedObjectSelector() (labels.Selector, error)

	// GetName gets the webhook Name field. Note that the name is scoped to the webhook
	// configuration and does not provide a globally unique identity, if a unique identity is
	// needed, use GetUID.
	GetName() string
	// GetClientConfig gets the webhook ClientConfig field.
	GetClientConfig() v1beta1.WebhookClientConfig
	// GetRules gets the webhook Rules field.
	GetRules() []v1beta1.RuleWithOperations
	// GetFailurePolicy gets the webhook FailurePolicy field.
	GetFailurePolicy() *v1beta1.FailurePolicyType
	// GetMatchPolicy gets the webhook MatchPolicy field.
	GetMatchPolicy() *v1beta1.MatchPolicyType
	// GetNamespaceSelector gets the webhook NamespaceSelector field.
	GetNamespaceSelector() *metav1.LabelSelector
	// GetObjectSelector gets the webhook ObjectSelector field.
	GetObjectSelector() *metav1.LabelSelector
	// GetSideEffects gets the webhook SideEffects field.
	GetSideEffects() *v1beta1.SideEffectClass
	// GetTimeoutSeconds gets the webhook TimeoutSeconds field.
	GetTimeoutSeconds() *int32
	// GetAdmissionReviewVersions gets the webhook AdmissionReviewVersions field.
	GetAdmissionReviewVersions() []string

	// GetMutatingWebhook if the accessor contains a MutatingWebhook, returns it and true, else returns false.
	GetMutatingWebhook() (*v1beta1.MutatingWebhook, bool)
	// GetValidatingWebhook if the accessor contains a ValidatingWebhook, returns it and true, else returns false.
	GetValidatingWebhook() (*v1beta1.ValidatingWebhook, bool)
}

// NewMutatingWebhookAccessor creates an accessor for a MutatingWebhook.
func NewMutatingWebhookAccessor(uid, configurationName string, h *v1beta1.MutatingWebhook) WebhookAccessor {
	return &mutatingWebhookAccessor{uid: uid, configurationName: configurationName, MutatingWebhook: h}
}

type mutatingWebhookAccessor struct {
	*v1beta1.MutatingWebhook
	uid               string
	configurationName string

	initObjectSelector sync.Once
	objectSelector     labels.Selector
	objectSelectorErr  error

	initNamespaceSelector sync.Once
	namespaceSelector     labels.Selector
	namespaceSelectorErr  error

	initClient sync.Once
	client     *rest.RESTClient
	clientErr  error
}

func (m *mutatingWebhookAccessor) GetUID() string {
	return m.uid
}

func (m *mutatingWebhookAccessor) GetConfigurationName() string {
	return m.configurationName
}

func (m *mutatingWebhookAccessor) GetRESTClient(clientManager *webhookutil.ClientManager) (*rest.RESTClient, error) {
	m.initClient.Do(func() {
		m.client, m.clientErr = nil, fmt.Errorf("unimplemented")
	})
	return m.client, m.clientErr
}

func (m *mutatingWebhookAccessor) GetParsedNamespaceSelector() (labels.Selector, error) {
	m.initNamespaceSelector.Do(func() {
		m.namespaceSelector, m.namespaceSelectorErr = metav1.LabelSelectorAsSelector(m.NamespaceSelector)
	})
	return m.namespaceSelector, m.namespaceSelectorErr
}

func (m *mutatingWebhookAccessor) GetParsedObjectSelector() (labels.Selector, error) {
	m.initObjectSelector.Do(func() {
		m.objectSelector, m.objectSelectorErr = metav1.LabelSelectorAsSelector(m.ObjectSelector)
	})
	return m.objectSelector, m.objectSelectorErr
}

func (m *mutatingWebhookAccessor) GetName() string {
	return m.Name
}

func (m *mutatingWebhookAccessor) GetClientConfig() v1beta1.WebhookClientConfig {
	return m.ClientConfig
}

func (m *mutatingWebhookAccessor) GetRules() []v1beta1.RuleWithOperations {
	return m.Rules
}

func (m *mutatingWebhookAccessor) GetFailurePolicy() *v1beta1.FailurePolicyType {
	return m.FailurePolicy
}

func (m *mutatingWebhookAccessor) GetMatchPolicy() *v1beta1.MatchPolicyType {
	return m.MatchPolicy
}

func (m *mutatingWebhookAccessor) GetNamespaceSelector() *metav1.LabelSelector {
	return m.NamespaceSelector
}

func (m *mutatingWebhookAccessor) GetObjectSelector() *metav1.LabelSelector {
	return m.ObjectSelector
}

func (m *mutatingWebhookAccessor) GetSideEffects() *v1beta1.SideEffectClass {
	return m.SideEffects
}

func (m *mutatingWebhookAccessor) GetTimeoutSeconds() *int32 {
	return m.TimeoutSeconds
}

func (m *mutatingWebhookAccessor) GetAdmissionReviewVersions() []string {
	return m.AdmissionReviewVersions
}

func (m *mutatingWebhookAccessor) GetMutatingWebhook() (*v1beta1.MutatingWebhook, bool) {
	return m.MutatingWebhook, true
}

func (m *mutatingWebhookAccessor) GetValidatingWebhook() (*v1beta1.ValidatingWebhook, bool) {
	return nil, false
}

// NewValidatingWebhookAccessor creates an accessor for a ValidatingWebhook.
func NewValidatingWebhookAccessor(uid, configurationName string, h *v1beta1.ValidatingWebhook) WebhookAccessor {
	return &validatingWebhookAccessor{uid: uid, configurationName: configurationName, ValidatingWebhook: h}
}

type validatingWebhookAccessor struct {
	*v1beta1.ValidatingWebhook
	uid               string
	configurationName string

	initObjectSelector sync.Once
	objectSelector     labels.Selector
	objectSelectorErr  error

	initNamespaceSelector sync.Once
	namespaceSelector     labels.Selector
	namespaceSelectorErr  error

	initClient sync.Once
	client     *rest.RESTClient
	clientErr  error
}

func (v *validatingWebhookAccessor) GetUID() string {
	return v.uid
}

func (v *validatingWebhookAccessor) GetConfigurationName() string {
	return v.configurationName
}

func (v *validatingWebhookAccessor) GetRESTClient(clientManager *webhookutil.ClientManager) (*rest.RESTClient, error) {
	v.initClient.Do(func() {
		v.client, v.clientErr = nil, fmt.Errorf("unimplemented")
	})
	return v.client, v.clientErr
}

func (v *validatingWebhookAccessor) GetParsedNamespaceSelector() (labels.Selector, error) {
	v.initNamespaceSelector.Do(func() {
		v.namespaceSelector, v.namespaceSelectorErr = metav1.LabelSelectorAsSelector(v.NamespaceSelector)
	})
	return v.namespaceSelector, v.namespaceSelectorErr
}

func (v *validatingWebhookAccessor) GetParsedObjectSelector() (labels.Selector, error) {
	v.initObjectSelector.Do(func() {
		v.objectSelector, v.objectSelectorErr = metav1.LabelSelectorAsSelector(v.ObjectSelector)
	})
	return v.objectSelector, v.objectSelectorErr
}

func (v *validatingWebhookAccessor) GetName() string {
	return v.Name
}

func (v *validatingWebhookAccessor) GetClientConfig() v1beta1.WebhookClientConfig {
	return v.ClientConfig
}

func (v *validatingWebhookAccessor) GetRules() []v1beta1.RuleWithOperations {
	return v.Rules
}

func (v *validatingWebhookAccessor) GetFailurePolicy() *v1beta1.FailurePolicyType {
	return v.FailurePolicy
}

func (v *validatingWebhookAccessor) GetMatchPolicy() *v1beta1.MatchPolicyType {
	return v.MatchPolicy
}

func (v *validatingWebhookAccessor) GetNamespaceSelector() *metav1.LabelSelector {
	return v.NamespaceSelector
}

func (v *validatingWebhookAccessor) GetObjectSelector() *metav1.LabelSelector {
	return v.ObjectSelector
}

func (v *validatingWebhookAccessor) GetSideEffects() *v1beta1.SideEffectClass {
	return v.SideEffects
}

func (v *validatingWebhookAccessor) GetTimeoutSeconds() *int32 {
	return v.TimeoutSeconds
}

func (v *validatingWebhookAccessor) GetAdmissionReviewVersions() []string {
	return v.AdmissionReviewVersions
}

func (v *validatingWebhookAccessor) GetMutatingWebhook() (*v1beta1.MutatingWebhook, bool) {
	return nil, false
}

func (v *validatingWebhookAccessor) GetValidatingWebhook() (*v1beta1.ValidatingWebhook, bool) {
	return v.ValidatingWebhook, true
}
<|endoftext|>


<commit_before>package axis

// Positionable is the interface for positionable
// items on a axis
type Positionable interface {
	Current() Position
	Since(Position) Distance
}

// Sleepable is the interface for sleepable provider
type Sleepable interface {
	Sleep(Distance)
}

// Trigger is the interface that wraps methods
// to define triggers
type Trigger interface {
	After(Distance) <-chan Position
	AfterFunc(Distance, func(Position)) Watcher
	AfterChan(Distance, chan Position) Watcher
}

// Provider is the interface that wraps methods
// to manipulate position
type Provider interface {
        Positionable
        Sleepable
        Trigger
}

// UpdatableProvider is the interface which allow
// to update the position of the provider
type UpdatableProvider interface {
	Provider
	Update(Position)
}
<commit_msg>Fix typo<commit_after>package axis

// Positionable is the interface for positionable
// items on an axis
type Positionable interface {
	Current() Position
	Since(Position) Distance
}

// Sleepable is the interface for sleepable provider
type Sleepable interface {
	Sleep(Distance)
}

// Trigger is the interface that wraps methods
// to define triggers
type Trigger interface {
	After(Distance) <-chan Position
	AfterFunc(Distance, func(Position)) Watcher
	AfterChan(Distance, chan Position) Watcher
}

// Provider is the interface that wraps methods
// to manipulate position
type Provider interface {
        Positionable
        Sleepable
        Trigger
}

// UpdatableProvider is the interface which allow
// to update the position of the provider
type UpdatableProvider interface {
	Provider
	Update(Position)
}
<|endoftext|>


<commit_before>// Copyright 2016 Florian Pigorsch. All rights reserved.
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package sm

import "fmt"

// TileProvider encapsulates all infos about a map tile provider service (name, url scheme, attribution, etc.)
type TileProvider struct {
	Name        string
	Attribution string
	TileSize    int
	URLPattern  string // "%[1]s" => shard, "%[2]d" => zoom, "%[3]d" => x, "%[4]d" => y
	Shards      []string
}

func (t *TileProvider) getURL(shard string, zoom, x, y int) string {
	return fmt.Sprintf(t.URLPattern, shard, zoom, x, y)
}

// NewTileProviderMapQuest creates a TileProvider struct for mapquest's tile service
func NewTileProviderMapQuest() *TileProvider {
	t := new(TileProvider)
	t.Name = "mapquest"
	t.Attribution = "Maps (c) MapQuest; Data (c) OSM and contributors, ODbL"
	t.TileSize = 256
	t.URLPattern = "http://otile%[1]s.mqcdn.com/tiles/1.0.0/osm/%[2]d/%[3]d/%[4]d.png"
	t.Shards = []string{"1", "2", "3", "4"}
	return t
}

func newTileProviderThunderforest(name string) *TileProvider {
	t := new(TileProvider)
	t.Name = fmt.Sprintf("thunderforest-%s", name)
	t.Attribution = "Maps (c) Thundeforest; Data (c) OSM and contributors, ODbL"
	t.TileSize = 256
	t.URLPattern = "https://%[1]s.tile.thunderforest.com/" + name + "/%[2]d/%[3]d/%[4]d.png"
	t.Shards = []string{"a", "b", "c"}
	return t
}

// NewTileProviderThunderforestLandscape creates a TileProvider struct for thundeforests's 'landscape' tile service
func NewTileProviderThunderforestLandscape() *TileProvider {
	return newTileProviderThunderforest("landscape")
}

// NewTileProviderThunderforestOutdoors creates a TileProvider struct for thundeforests's 'outdoors' tile service
func NewTileProviderThunderforestOutdoors() *TileProvider {
	return newTileProviderThunderforest("outdoors")
}

// NewTileProviderThunderforestTransport creates a TileProvider struct for thundeforests's 'transport' tile service
func NewTileProviderThunderforestTransport() *TileProvider {
	return newTileProviderThunderforest("transport")
}

// NewTileProviderStamenToner creates a TileProvider struct for stamens' 'toner' tile service
func NewTileProviderStamenToner() *TileProvider {
	t := new(TileProvider)
	t.Name = "stamen-toner"
	t.Attribution = "Maps (c) Stamen; Data (c) OSM and contributors, ODbL"
	t.TileSize = 256
	t.URLPattern = "http://%[1]s.tile.stamen.com/toner/%[2]d/%[3]d/%[4]d.png"
	t.Shards = []string{"a", "b", "c", "d"}
	return t
}

// NewTileProviderOpenTopoMap creates a TileProvider struct for opentopomaps's tile service
func NewTileProviderOpenTopoMap() *TileProvider {
	t := new(TileProvider)
	t.Name = "opentopomap"
	t.Attribution = "Maps (c) OpenTopoMap [CC-BY-SA]; Data (c) OSM and contributors [ODbL]; Data (c) SRTM"
	t.TileSize = 256
	t.URLPattern = "http://%[1].tile.opentopomap.org/%[2]d/%[3]d/%[4]d.png"
	t.Shards = []string{"a", "b", "c"}
	return t
}

// GetTileProviders returns a map of all available TileProviders
func GetTileProviders() map[string]*TileProvider {
	m := make(map[string]*TileProvider)

	list := []*TileProvider{
		NewTileProviderMapQuest(),
		NewTileProviderThunderforestLandscape(),
		NewTileProviderThunderforestOutdoors(),
		NewTileProviderThunderforestTransport(),
		NewTileProviderStamenToner(),
        NewTileProviderOpenTopoMap()}

	for _, tp := range list {
		m[tp.Name] = tp
	}

	return m
}
<commit_msg>fixed otm tile provider<commit_after>// Copyright 2016 Florian Pigorsch. All rights reserved.
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package sm

import "fmt"

// TileProvider encapsulates all infos about a map tile provider service (name, url scheme, attribution, etc.)
type TileProvider struct {
	Name        string
	Attribution string
	TileSize    int
	URLPattern  string // "%[1]s" => shard, "%[2]d" => zoom, "%[3]d" => x, "%[4]d" => y
	Shards      []string
}

func (t *TileProvider) getURL(shard string, zoom, x, y int) string {
	return fmt.Sprintf(t.URLPattern, shard, zoom, x, y)
}

// NewTileProviderMapQuest creates a TileProvider struct for mapquest's tile service
func NewTileProviderMapQuest() *TileProvider {
	t := new(TileProvider)
	t.Name = "mapquest"
	t.Attribution = "Maps (c) MapQuest; Data (c) OSM and contributors, ODbL"
	t.TileSize = 256
	t.URLPattern = "http://otile%[1]s.mqcdn.com/tiles/1.0.0/osm/%[2]d/%[3]d/%[4]d.png"
	t.Shards = []string{"1", "2", "3", "4"}
	return t
}

func newTileProviderThunderforest(name string) *TileProvider {
	t := new(TileProvider)
	t.Name = fmt.Sprintf("thunderforest-%s", name)
	t.Attribution = "Maps (c) Thundeforest; Data (c) OSM and contributors, ODbL"
	t.TileSize = 256
	t.URLPattern = "https://%[1]s.tile.thunderforest.com/" + name + "/%[2]d/%[3]d/%[4]d.png"
	t.Shards = []string{"a", "b", "c"}
	return t
}

// NewTileProviderThunderforestLandscape creates a TileProvider struct for thundeforests's 'landscape' tile service
func NewTileProviderThunderforestLandscape() *TileProvider {
	return newTileProviderThunderforest("landscape")
}

// NewTileProviderThunderforestOutdoors creates a TileProvider struct for thundeforests's 'outdoors' tile service
func NewTileProviderThunderforestOutdoors() *TileProvider {
	return newTileProviderThunderforest("outdoors")
}

// NewTileProviderThunderforestTransport creates a TileProvider struct for thundeforests's 'transport' tile service
func NewTileProviderThunderforestTransport() *TileProvider {
	return newTileProviderThunderforest("transport")
}

// NewTileProviderStamenToner creates a TileProvider struct for stamens' 'toner' tile service
func NewTileProviderStamenToner() *TileProvider {
	t := new(TileProvider)
	t.Name = "stamen-toner"
	t.Attribution = "Maps (c) Stamen; Data (c) OSM and contributors, ODbL"
	t.TileSize = 256
	t.URLPattern = "http://%[1]s.tile.stamen.com/toner/%[2]d/%[3]d/%[4]d.png"
	t.Shards = []string{"a", "b", "c", "d"}
	return t
}

// NewTileProviderOpenTopoMap creates a TileProvider struct for opentopomaps's tile service
func NewTileProviderOpenTopoMap() *TileProvider {
	t := new(TileProvider)
	t.Name = "opentopomap"
	t.Attribution = "Maps (c) OpenTopoMap [CC-BY-SA]; Data (c) OSM and contributors [ODbL]; Data (c) SRTM"
	t.TileSize = 256
	t.URLPattern = "http://%[1]s.tile.opentopomap.org/%[2]d/%[3]d/%[4]d.png"
	t.Shards = []string{"a", "b", "c"}
	return t
}

// GetTileProviders returns a map of all available TileProviders
func GetTileProviders() map[string]*TileProvider {
	m := make(map[string]*TileProvider)

	list := []*TileProvider{
		NewTileProviderMapQuest(),
		NewTileProviderThunderforestLandscape(),
		NewTileProviderThunderforestOutdoors(),
		NewTileProviderThunderforestTransport(),
		NewTileProviderStamenToner(),
		NewTileProviderOpenTopoMap()}

	for _, tp := range list {
		m[tp.Name] = tp
	}

	return m
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"os"
	"strings"
	"text/tabwriter"
	"time"

	"github.com/go-kit/kit/log/term"
	metrics "github.com/rcrowley/go-metrics"

	tmtypes "github.com/tendermint/tendermint/types"
	"github.com/tendermint/tmlibs/log"
	"github.com/tendermint/tools/tm-monitor/monitor"
)

var version = "0.2.1"

var logger = log.NewNopLogger()

type statistics struct {
	BlockTimeSample    metrics.Histogram
	TxThroughputSample metrics.Histogram
	BlockLatency       metrics.Histogram
}

func main() {
	var duration, txsRate, connections int
	var verbose bool

	flag.IntVar(&connections, "c", 1, "Connections to keep open per endpoint")
	flag.IntVar(&duration, "T", 10, "Exit after the specified amount of time in seconds")
	flag.IntVar(&txsRate, "r", 1000, "Txs per second to send in a connection")
	flag.BoolVar(&verbose, "v", false, "Verbose output")

	flag.Usage = func() {
		fmt.Println(`Tendermint blockchain benchmarking tool.

Usage:
	tm-bench [-c 1] [-T 10] [-r 1000] [endpoints]

Examples:
	tm-bench localhost:46657`)
		fmt.Println("Flags:")
		flag.PrintDefaults()
	}

	flag.Parse()

	if flag.NArg() == 0 {
		flag.Usage()
		os.Exit(1)
	}

	if verbose {
		// Color errors red
		colorFn := func(keyvals ...interface{}) term.FgBgColor {
			for i := 1; i < len(keyvals); i += 2 {
				if _, ok := keyvals[i].(error); ok {
					return term.FgBgColor{Fg: term.White, Bg: term.Red}
				}
			}
			return term.FgBgColor{}
		}
		logger = log.NewTMLoggerWithColorFn(log.NewSyncWriter(os.Stdout), colorFn)
	}

	fmt.Printf("Running %ds test @ %s\n", duration, flag.Arg(0))

	endpoints := strings.Split(flag.Arg(0), ",")

	blockCh := make(chan tmtypes.Header, 100)
	blockLatencyCh := make(chan float64, 100)

	nodes := startNodes(endpoints, blockCh, blockLatencyCh)

	transacters := startTransacters(endpoints, connections, txsRate)

	stats := &statistics{
		BlockTimeSample:    metrics.NewHistogram(metrics.NewUniformSample(1000)),
		TxThroughputSample: metrics.NewHistogram(metrics.NewUniformSample(1000)),
		BlockLatency:       metrics.NewHistogram(metrics.NewUniformSample(1000)),
	}

	lastBlockHeight := int64(-1)

	durationTimer := time.After(time.Duration(duration) * time.Second)
	ticker := time.NewTicker(1 * time.Second)
	var blocks int
	var txs int64
	for {
		select {
		case b := <-blockCh:
			if lastBlockHeight < b.Height {
				blocks++
				txs += b.NumTxs
				lastBlockHeight = b.Height
			}
		case l := <-blockLatencyCh:
			stats.BlockLatency.Update(int64(l))
		case <-ticker.C:
			stats.BlockTimeSample.Update(int64(blocks))
			stats.TxThroughputSample.Update(txs)
			blocks = 0
			txs = 0
		case <-durationTimer:
			for _, t := range transacters {
				t.Stop()
			}

			printStatistics(stats)

			for _, n := range nodes {
				n.Stop()
			}
			return
		}
	}
}

func startNodes(endpoints []string, blockCh chan<- tmtypes.Header, blockLatencyCh chan<- float64) []*monitor.Node {
	nodes := make([]*monitor.Node, len(endpoints))

	for i, e := range endpoints {
		n := monitor.NewNode(e)
		n.SetLogger(logger.With("node", e))
		n.SendBlocksTo(blockCh)
		n.SendBlockLatenciesTo(blockLatencyCh)
		if err := n.Start(); err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		nodes[i] = n
	}

	return nodes
}

func startTransacters(endpoints []string, connections int, txsRate int) []*transacter {
	transacters := make([]*transacter, len(endpoints))

	for i, e := range endpoints {
		t := newTransacter(e, connections, txsRate)
		t.SetLogger(logger)
		if err := t.Start(); err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		transacters[i] = t
	}

	return transacters
}

func printStatistics(stats *statistics) {
	w := tabwriter.NewWriter(os.Stdout, 0, 0, 5, ' ', 0)
	fmt.Fprintln(w, "Stats\tAvg\tStdev\tMax\t")
	fmt.Fprintln(w, fmt.Sprintf("Block latency\t%.2fms\t%.2fms\t%dms\t",
		stats.BlockLatency.Mean()/1000000.0,
		stats.BlockLatency.StdDev()/1000000.0,
		stats.BlockLatency.Max()/1000000))
	fmt.Fprintln(w, fmt.Sprintf("Blocks/sec\t%.3f\t%.3f\t%d\t",
		stats.BlockTimeSample.Mean(),
		stats.BlockTimeSample.StdDev(),
		stats.BlockTimeSample.Max()))
	fmt.Fprintln(w, fmt.Sprintf("Txs/sec\t%.0f\t%.0f\t%d\t",
		stats.TxThroughputSample.Mean(),
		stats.TxThroughputSample.StdDev(),
		stats.TxThroughputSample.Max()))
	w.Flush()
}
<commit_msg>[bench] bump version<commit_after>package main

import (
	"flag"
	"fmt"
	"os"
	"strings"
	"text/tabwriter"
	"time"

	"github.com/go-kit/kit/log/term"
	metrics "github.com/rcrowley/go-metrics"

	tmtypes "github.com/tendermint/tendermint/types"
	"github.com/tendermint/tmlibs/log"
	"github.com/tendermint/tools/tm-monitor/monitor"
)

var version = "0.3.0"

var logger = log.NewNopLogger()

type statistics struct {
	BlockTimeSample    metrics.Histogram
	TxThroughputSample metrics.Histogram
	BlockLatency       metrics.Histogram
}

func main() {
	var duration, txsRate, connections int
	var verbose bool

	flag.IntVar(&connections, "c", 1, "Connections to keep open per endpoint")
	flag.IntVar(&duration, "T", 10, "Exit after the specified amount of time in seconds")
	flag.IntVar(&txsRate, "r", 1000, "Txs per second to send in a connection")
	flag.BoolVar(&verbose, "v", false, "Verbose output")

	flag.Usage = func() {
		fmt.Println(`Tendermint blockchain benchmarking tool.

Usage:
	tm-bench [-c 1] [-T 10] [-r 1000] [endpoints]

Examples:
	tm-bench localhost:46657`)
		fmt.Println("Flags:")
		flag.PrintDefaults()
	}

	flag.Parse()

	if flag.NArg() == 0 {
		flag.Usage()
		os.Exit(1)
	}

	if verbose {
		// Color errors red
		colorFn := func(keyvals ...interface{}) term.FgBgColor {
			for i := 1; i < len(keyvals); i += 2 {
				if _, ok := keyvals[i].(error); ok {
					return term.FgBgColor{Fg: term.White, Bg: term.Red}
				}
			}
			return term.FgBgColor{}
		}
		logger = log.NewTMLoggerWithColorFn(log.NewSyncWriter(os.Stdout), colorFn)
	}

	fmt.Printf("Running %ds test @ %s\n", duration, flag.Arg(0))

	endpoints := strings.Split(flag.Arg(0), ",")

	blockCh := make(chan tmtypes.Header, 100)
	blockLatencyCh := make(chan float64, 100)

	nodes := startNodes(endpoints, blockCh, blockLatencyCh)

	transacters := startTransacters(endpoints, connections, txsRate)

	stats := &statistics{
		BlockTimeSample:    metrics.NewHistogram(metrics.NewUniformSample(1000)),
		TxThroughputSample: metrics.NewHistogram(metrics.NewUniformSample(1000)),
		BlockLatency:       metrics.NewHistogram(metrics.NewUniformSample(1000)),
	}

	lastBlockHeight := int64(-1)

	durationTimer := time.After(time.Duration(duration) * time.Second)
	ticker := time.NewTicker(1 * time.Second)
	var blocks int
	var txs int64
	for {
		select {
		case b := <-blockCh:
			if lastBlockHeight < b.Height {
				blocks++
				txs += b.NumTxs
				lastBlockHeight = b.Height
			}
		case l := <-blockLatencyCh:
			stats.BlockLatency.Update(int64(l))
		case <-ticker.C:
			stats.BlockTimeSample.Update(int64(blocks))
			stats.TxThroughputSample.Update(txs)
			blocks = 0
			txs = 0
		case <-durationTimer:
			for _, t := range transacters {
				t.Stop()
			}

			printStatistics(stats)

			for _, n := range nodes {
				n.Stop()
			}
			return
		}
	}
}

func startNodes(endpoints []string, blockCh chan<- tmtypes.Header, blockLatencyCh chan<- float64) []*monitor.Node {
	nodes := make([]*monitor.Node, len(endpoints))

	for i, e := range endpoints {
		n := monitor.NewNode(e)
		n.SetLogger(logger.With("node", e))
		n.SendBlocksTo(blockCh)
		n.SendBlockLatenciesTo(blockLatencyCh)
		if err := n.Start(); err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		nodes[i] = n
	}

	return nodes
}

func startTransacters(endpoints []string, connections int, txsRate int) []*transacter {
	transacters := make([]*transacter, len(endpoints))

	for i, e := range endpoints {
		t := newTransacter(e, connections, txsRate)
		t.SetLogger(logger)
		if err := t.Start(); err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		transacters[i] = t
	}

	return transacters
}

func printStatistics(stats *statistics) {
	w := tabwriter.NewWriter(os.Stdout, 0, 0, 5, ' ', 0)
	fmt.Fprintln(w, "Stats\tAvg\tStdev\tMax\t")
	fmt.Fprintln(w, fmt.Sprintf("Block latency\t%.2fms\t%.2fms\t%dms\t",
		stats.BlockLatency.Mean()/1000000.0,
		stats.BlockLatency.StdDev()/1000000.0,
		stats.BlockLatency.Max()/1000000))
	fmt.Fprintln(w, fmt.Sprintf("Blocks/sec\t%.3f\t%.3f\t%d\t",
		stats.BlockTimeSample.Mean(),
		stats.BlockTimeSample.StdDev(),
		stats.BlockTimeSample.Max()))
	fmt.Fprintln(w, fmt.Sprintf("Txs/sec\t%.0f\t%.0f\t%d\t",
		stats.TxThroughputSample.Mean(),
		stats.TxThroughputSample.StdDev(),
		stats.TxThroughputSample.Max()))
	w.Flush()
}
<|endoftext|>


<commit_before>package doc

import (
	"errors"
	"github.com/russross/blackfriday"
	"html"
	"strings"
)

type Format string

const (
	Text             Format = "text"
	Markdown         Format = "markdown"
	ReStructuredText Format = "rst"
)

// ToHTML converts a source document in format to an HTML string. If conversion
// fails, it returns a failsafe plaintext-to-HTML conversion and a non-nil error.
func ToHTML(format Format, source string) (htmlSource string, err error) {
	switch format {
	case Markdown:
		var out []byte
		out = blackfriday.MarkdownCommon([]byte(source))
		htmlSource = string(out)
	case ReStructuredText:
		htmlSource, err = ReStructuredTextToHTML(source)
	case Text:
	default:
		err = ErrUnhandledFormat
	}
	if err != nil || htmlSource == "" {
		htmlSource = "
" + strings.TrimSpace(html.EscapeString(source)) + "
"
	}
	return
}

var ErrUnhandledFormat = errors.New("unhandled doc format")
<commit_msg>update blackfriday, handle ~~~<commit_after>package doc

import (
	"errors"
	"html"
	"strings"

	"github.com/russross/blackfriday"
)

type Format string

const (
	Text             Format = "text"
	Markdown         Format = "markdown"
	ReStructuredText Format = "rst"
)

// ToHTML converts a source document in format to an HTML string. If conversion
// fails, it returns a failsafe plaintext-to-HTML conversion and a non-nil error.
func ToHTML(format Format, source string) (htmlSource string, err error) {
	switch format {
	case Markdown:
		// Some README.md files use "~~~" instead of "```" for delimiting code
		// blocks. But "~~~" is not supported by blackfriday, so hackily replace
		// the former with the latter. See, e.g., the code blocks at
		// https://raw.githubusercontent.com/go-martini/martini/de643861770082784ad14cba4557ad68568dcc7b/README.md.
		source = strings.Replace(source, "\n~~~", "\n```", -1)

		var out []byte
		out = blackfriday.MarkdownCommon([]byte(source))
		htmlSource = string(out)
	case ReStructuredText:
		htmlSource, err = ReStructuredTextToHTML(source)
	case Text:
	default:
		err = ErrUnhandledFormat
	}
	if err != nil || htmlSource == "" {
		htmlSource = "
" + strings.TrimSpace(html.EscapeString(source)) + "
"
	}
	return
}

var ErrUnhandledFormat = errors.New("unhandled doc format")
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.

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

package tchannel

import (
	"log"
	"time"
)

// When reporting spans, we report:
// bunch of "binary" annotations (eg

// AnnotationKey is the key for annotations.
type AnnotationKey string

// Known annotation keys
const (
	AnnotationKeyClientSend    = "cs"
	AnnotationKeyClientReceive = "cr"
	AnnotationKeyServerSend    = "ss"
	AnnotationKeyServerReceive = "sr"
)

// BinaryAnnotation is additional context information about the span.
type BinaryAnnotation struct {
	Key string
	// Value contains one of: string, float64, bool, []byte, int64
	Value interface{}
}

// Annotation represents a specific event and the timestamp at which it occurred.
type Annotation struct {
	Key       AnnotationKey
	Timestamp time.Time
}

// NewAnnotation returns a new annotation.
func NewAnnotation(key AnnotationKey) Annotation {
	return Annotation{Key: key, Timestamp: timeNow()}
}

// NewBinaryAnnotation returns a new binary annotation.
func NewBinaryAnnotation(key string, value interface{}) BinaryAnnotation {
	return BinaryAnnotation{Key: key, Value: value}
}

// TraceReporter is the interface used to report Trace spans.
type TraceReporter interface {
	// Report method is intended to report Span information.
	// It returns any error encountered otherwise nil.
	Report(span Span, annotations []Annotation, binaryAnnotations []BinaryAnnotation, targetEndpoint TargetEndpoint)
}

// NullReporter is the default TraceReporter which does not do anything.
var NullReporter TraceReporter = nullReporter{}

type nullReporter struct{}

func (nullReporter) Report(_ Span, _ []Annotation, _ []BinaryAnnotation, _ TargetEndpoint) {
}

// SimpleTraceReporter is a trace reporter which prints using the default logger.
var SimpleTraceReporter TraceReporter = simpleTraceReporter{}

type simpleTraceReporter struct{}

func (simpleTraceReporter) Report(
	span Span, annotations []Annotation, binaryAnnotations []BinaryAnnotation, targetEndpoint TargetEndpoint) {
	log.Printf("SimpleTraceReporter.Report span: %+v annotations: %+v binaryAnnotations: %+v targetEndpoint: %+v",
		span, annotations, binaryAnnotations, targetEndpoint)
}

// Annotations is am embeddable struct used to track annotations.
type Annotations struct {
	binaryAnnotations []BinaryAnnotation
	annotations       []Annotation
}

// AddBinaryAnnotation adds a binary annotation.
func (as *Annotations) AddBinaryAnnotation(binaryAnnotation BinaryAnnotation) {
	as.binaryAnnotations = append(as.binaryAnnotations, binaryAnnotation)
}

// AddAnnotation adds a standard annotation.
func (as *Annotations) AddAnnotation(key AnnotationKey) {
	as.annotations = append(as.annotations, NewAnnotation(key))
}

// Report reports the annotations to the given trace reporter.
func (as *Annotations) Report(span Span, targetEndpoint TargetEndpoint, reporter TraceReporter) {
	reporter.Report(span, as.annotations, as.binaryAnnotations, targetEndpoint)
}
<commit_msg>Only Report traces if Tracing is enabled<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.

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

package tchannel

import (
	"log"
	"time"
)

// When reporting spans, we report:
// bunch of "binary" annotations (eg

// AnnotationKey is the key for annotations.
type AnnotationKey string

// Known annotation keys
const (
	AnnotationKeyClientSend    = "cs"
	AnnotationKeyClientReceive = "cr"
	AnnotationKeyServerSend    = "ss"
	AnnotationKeyServerReceive = "sr"
)

// BinaryAnnotation is additional context information about the span.
type BinaryAnnotation struct {
	Key string
	// Value contains one of: string, float64, bool, []byte, int64
	Value interface{}
}

// Annotation represents a specific event and the timestamp at which it occurred.
type Annotation struct {
	Key       AnnotationKey
	Timestamp time.Time
}

// NewAnnotation returns a new annotation.
func NewAnnotation(key AnnotationKey) Annotation {
	return Annotation{Key: key, Timestamp: timeNow()}
}

// NewBinaryAnnotation returns a new binary annotation.
func NewBinaryAnnotation(key string, value interface{}) BinaryAnnotation {
	return BinaryAnnotation{Key: key, Value: value}
}

// TraceReporter is the interface used to report Trace spans.
type TraceReporter interface {
	// Report method is intended to report Span information.
	// It returns any error encountered otherwise nil.
	Report(span Span, annotations []Annotation, binaryAnnotations []BinaryAnnotation, targetEndpoint TargetEndpoint)
}

// NullReporter is the default TraceReporter which does not do anything.
var NullReporter TraceReporter = nullReporter{}

type nullReporter struct{}

func (nullReporter) Report(_ Span, _ []Annotation, _ []BinaryAnnotation, _ TargetEndpoint) {
}

// SimpleTraceReporter is a trace reporter which prints using the default logger.
var SimpleTraceReporter TraceReporter = simpleTraceReporter{}

type simpleTraceReporter struct{}

func (simpleTraceReporter) Report(
	span Span, annotations []Annotation, binaryAnnotations []BinaryAnnotation, targetEndpoint TargetEndpoint) {
	log.Printf("SimpleTraceReporter.Report span: %+v annotations: %+v binaryAnnotations: %+v targetEndpoint: %+v",
		span, annotations, binaryAnnotations, targetEndpoint)
}

// Annotations is am embeddable struct used to track annotations.
type Annotations struct {
	binaryAnnotations []BinaryAnnotation
	annotations       []Annotation
}

// AddBinaryAnnotation adds a binary annotation.
func (as *Annotations) AddBinaryAnnotation(binaryAnnotation BinaryAnnotation) {
	as.binaryAnnotations = append(as.binaryAnnotations, binaryAnnotation)
}

// AddAnnotation adds a standard annotation.
func (as *Annotations) AddAnnotation(key AnnotationKey) {
	as.annotations = append(as.annotations, NewAnnotation(key))
}

// Report reports the annotations to the given trace reporter, if tracing is enabled in the span.
func (as *Annotations) Report(span Span, targetEndpoint TargetEndpoint, reporter TraceReporter) {
	if span.TracingEnabled() {
		reporter.Report(span, as.annotations, as.binaryAnnotations, targetEndpoint)
	}
}
<|endoftext|>


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

package req

import (
	"bufio"
	"bytes"
	"crypto/rand"
	"fmt"
	"io"
	"net/http"
	"os"
	"reflect"
	"strings"
	"testing"
)

func TestBodyReadBadTrailer(t *testing.T) {
	b := &body{
		src: strings.NewReader("foobar"),
		hdr: true, // force reading the trailer
		r:   bufio.NewReader(strings.NewReader("")),
	}
	buf := make([]byte, 7)
	n, err := b.Read(buf[:3])
	got := string(buf[:n])
	if got != "foo" || err != nil {
		t.Fatalf(`first Read = %d (%q), %v; want 3 ("foo")`, n, got, err)
	}

	n, err = b.Read(buf[:])
	got = string(buf[:n])
	if got != "bar" || err != nil {
		t.Fatalf(`second Read = %d (%q), %v; want 3 ("bar")`, n, got, err)
	}

	n, err = b.Read(buf[:])
	got = string(buf[:n])
	if err == nil {
		t.Errorf("final Read was successful (%q), expected error from trailer read", got)
	}
}

func TestFinalChunkedBodyReadEOF(t *testing.T) {
	res, err := http.ReadResponse(bufio.NewReader(strings.NewReader(
		"HTTP/1.1 200 OK\r\n"+
			"Transfer-Encoding: chunked\r\n"+
			"\r\n"+
			"0a\r\n"+
			"Body here\n\r\n"+
			"09\r\n"+
			"continued\r\n"+
			"0\r\n"+
			"\r\n")), nil)
	if err != nil {
		t.Fatal(err)
	}
	want := "Body here\ncontinued"
	buf := make([]byte, len(want))
	n, err := res.Body.Read(buf)
	if n != len(want) || err != io.EOF {
		t.Logf("body = %#v", res.Body)
		t.Errorf("Read = %v, %v; want %d, EOF", n, err, len(want))
	}
	if string(buf) != want {
		t.Errorf("buf = %q; want %q", buf, want)
	}
}

func TestDetectInMemoryReaders(t *testing.T) {
	pr, _ := io.Pipe()
	tests := []struct {
		r    io.Reader
		want bool
	}{
		{pr, false},

		{bytes.NewReader(nil), true},
		{bytes.NewBuffer(nil), true},
		{strings.NewReader(""), true},

		{io.NopCloser(pr), false},

		{io.NopCloser(bytes.NewReader(nil)), true},
		{io.NopCloser(bytes.NewBuffer(nil)), true},
		{io.NopCloser(strings.NewReader("")), true},
	}
	for i, tt := range tests {
		got := isKnownInMemoryReader(tt.r)
		if got != tt.want {
			t.Errorf("%d: got = %v; want %v", i, got, tt.want)
		}
	}
}

type mockTransferWriter struct {
	CalledReader io.Reader
	WriteCalled  bool
}

var _ io.ReaderFrom = (*mockTransferWriter)(nil)

func (w *mockTransferWriter) ReadFrom(r io.Reader) (int64, error) {
	w.CalledReader = r
	return io.Copy(io.Discard, r)
}

func (w *mockTransferWriter) Write(p []byte) (int, error) {
	w.WriteCalled = true
	return io.Discard.Write(p)
}

func TestTransferWriterWriteBodyReaderTypes(t *testing.T) {
	fileType := reflect.TypeOf(&os.File{})
	bufferType := reflect.TypeOf(&bytes.Buffer{})

	nBytes := int64(1 << 10)
	newFileFunc := func() (r io.Reader, done func(), err error) {
		f, err := os.CreateTemp("", "net-http-newfilefunc")
		if err != nil {
			return nil, nil, err
		}

		// Write some bytes to the file to enable reading.
		if _, err := io.CopyN(f, rand.Reader, nBytes); err != nil {
			return nil, nil, fmt.Errorf("failed to write data to file: %v", err)
		}
		if _, err := f.Seek(0, 0); err != nil {
			return nil, nil, fmt.Errorf("failed to seek to front: %v", err)
		}

		done = func() {
			f.Close()
			os.Remove(f.Name())
		}

		return f, done, nil
	}

	newBufferFunc := func() (io.Reader, func(), error) {
		return bytes.NewBuffer(make([]byte, nBytes)), func() {}, nil
	}

	cases := []struct {
		name             string
		bodyFunc         func() (io.Reader, func(), error)
		method           string
		contentLength    int64
		transferEncoding []string
		limitedReader    bool
		expectedReader   reflect.Type
		expectedWrite    bool
	}{
		{
			name:           "file, non-chunked, size set",
			bodyFunc:       newFileFunc,
			method:         "PUT",
			contentLength:  nBytes,
			limitedReader:  true,
			expectedReader: fileType,
		},
		{
			name:   "file, non-chunked, size set, nopCloser wrapped",
			method: "PUT",
			bodyFunc: func() (io.Reader, func(), error) {
				r, cleanup, err := newFileFunc()
				return io.NopCloser(r), cleanup, err
			},
			contentLength:  nBytes,
			limitedReader:  true,
			expectedReader: fileType,
		},
		{
			name:           "file, non-chunked, negative size",
			method:         "PUT",
			bodyFunc:       newFileFunc,
			contentLength:  -1,
			expectedReader: fileType,
		},
		{
			name:           "file, non-chunked, CONNECT, negative size",
			method:         "CONNECT",
			bodyFunc:       newFileFunc,
			contentLength:  -1,
			expectedReader: fileType,
		},
		{
			name:             "file, chunked",
			method:           "PUT",
			bodyFunc:         newFileFunc,
			transferEncoding: []string{"chunked"},
			expectedWrite:    true,
		},
		{
			name:           "buffer, non-chunked, size set",
			bodyFunc:       newBufferFunc,
			method:         "PUT",
			contentLength:  nBytes,
			limitedReader:  true,
			expectedReader: bufferType,
		},
		{
			name:   "buffer, non-chunked, size set, nopCloser wrapped",
			method: "PUT",
			bodyFunc: func() (io.Reader, func(), error) {
				r, cleanup, err := newBufferFunc()
				return io.NopCloser(r), cleanup, err
			},
			contentLength:  nBytes,
			limitedReader:  true,
			expectedReader: bufferType,
		},
		{
			name:          "buffer, non-chunked, negative size",
			method:        "PUT",
			bodyFunc:      newBufferFunc,
			contentLength: -1,
			expectedWrite: true,
		},
		{
			name:          "buffer, non-chunked, CONNECT, negative size",
			method:        "CONNECT",
			bodyFunc:      newBufferFunc,
			contentLength: -1,
			expectedWrite: true,
		},
		{
			name:             "buffer, chunked",
			method:           "PUT",
			bodyFunc:         newBufferFunc,
			transferEncoding: []string{"chunked"},
			expectedWrite:    true,
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			body, cleanup, err := tc.bodyFunc()
			if err != nil {
				t.Fatal(err)
			}
			defer cleanup()

			mw := &mockTransferWriter{}
			tw := &transferWriter{
				Body:             body,
				ContentLength:    tc.contentLength,
				TransferEncoding: tc.transferEncoding,
			}

			if err := tw.writeBody(mw, nil); err != nil {
				t.Fatal(err)
			}

			if tc.expectedReader != nil {
				if mw.CalledReader == nil {
					t.Fatal("did not call ReadFrom")
				}

				var actualReader reflect.Type
				lr, ok := mw.CalledReader.(*io.LimitedReader)
				if ok && tc.limitedReader {
					actualReader = reflect.TypeOf(lr.R)
				} else {
					actualReader = reflect.TypeOf(mw.CalledReader)
				}

				if tc.expectedReader != actualReader {
					t.Fatalf("got reader %T want %T", actualReader, tc.expectedReader)
				}
			}

			if tc.expectedWrite && !mw.WriteCalled {
				t.Fatal("did not invoke Write")
			}
		})
	}
}

func TestParseTransferEncoding(t *testing.T) {
	tests := []struct {
		hdr     http.Header
		wantErr error
	}{
		{
			hdr:     http.Header{"Transfer-Encoding": {"fugazi"}},
			wantErr: &unsupportedTEError{`unsupported transfer encoding: "fugazi"`},
		},
		{
			hdr:     http.Header{"Transfer-Encoding": {"chunked, chunked", "identity", "chunked"}},
			wantErr: &unsupportedTEError{`too many transfer encodings: ["chunked, chunked" "identity" "chunked"]`},
		},
		{
			hdr:     http.Header{"Transfer-Encoding": {""}},
			wantErr: &unsupportedTEError{`unsupported transfer encoding: ""`},
		},
		{
			hdr:     http.Header{"Transfer-Encoding": {"chunked, identity"}},
			wantErr: &unsupportedTEError{`unsupported transfer encoding: "chunked, identity"`},
		},
		{
			hdr:     http.Header{"Transfer-Encoding": {"chunked", "identity"}},
			wantErr: &unsupportedTEError{`too many transfer encodings: ["chunked" "identity"]`},
		},
		{
			hdr:     http.Header{"Transfer-Encoding": {"\x0bchunked"}},
			wantErr: &unsupportedTEError{`unsupported transfer encoding: "\vchunked"`},
		},
		{
			hdr:     http.Header{"Transfer-Encoding": {"chunked"}},
			wantErr: nil,
		},
	}

	for i, tt := range tests {
		tr := &transferReader{
			Header:     tt.hdr,
			ProtoMajor: 1,
			ProtoMinor: 1,
		}
		gotErr := tr.parseTransferEncoding()
		if !reflect.DeepEqual(gotErr, tt.wantErr) {
			t.Errorf("%d.\ngot error:\n%v\nwant error:\n%v\n\n", i, gotErr, tt.wantErr)
		}
	}
}

// issue 39017 - disallow Content-Length values such as "+3"
func TestParseContentLength(t *testing.T) {
	tests := []struct {
		cl      string
		wantErr error
	}{
		{
			cl:      "3",
			wantErr: nil,
		},
		{
			cl:      "+3",
			wantErr: badStringError("bad Content-Length", "+3"),
		},
		{
			cl:      "-3",
			wantErr: badStringError("bad Content-Length", "-3"),
		},
		{
			// max int64, for safe conversion before returning
			cl:      "9223372036854775807",
			wantErr: nil,
		},
		{
			cl:      "9223372036854775808",
			wantErr: badStringError("bad Content-Length", "9223372036854775808"),
		},
	}

	for _, tt := range tests {
		if _, gotErr := parseContentLength(tt.cl); !reflect.DeepEqual(gotErr, tt.wantErr) {
			t.Errorf("%q:\n\tgot=%v\n\twant=%v", tt.cl, gotErr, tt.wantErr)
		}
	}
}
<commit_msg>net/http: correctly show error types in transfer test<commit_after>// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package req

import (
	"bufio"
	"bytes"
	"crypto/rand"
	"fmt"
	"io"
	"net/http"
	"os"
	"reflect"
	"strings"
	"testing"
)

func TestBodyReadBadTrailer(t *testing.T) {
	b := &body{
		src: strings.NewReader("foobar"),
		hdr: true, // force reading the trailer
		r:   bufio.NewReader(strings.NewReader("")),
	}
	buf := make([]byte, 7)
	n, err := b.Read(buf[:3])
	got := string(buf[:n])
	if got != "foo" || err != nil {
		t.Fatalf(`first Read = %d (%q), %v; want 3 ("foo")`, n, got, err)
	}

	n, err = b.Read(buf[:])
	got = string(buf[:n])
	if got != "bar" || err != nil {
		t.Fatalf(`second Read = %d (%q), %v; want 3 ("bar")`, n, got, err)
	}

	n, err = b.Read(buf[:])
	got = string(buf[:n])
	if err == nil {
		t.Errorf("final Read was successful (%q), expected error from trailer read", got)
	}
}

func TestFinalChunkedBodyReadEOF(t *testing.T) {
	res, err := http.ReadResponse(bufio.NewReader(strings.NewReader(
		"HTTP/1.1 200 OK\r\n"+
			"Transfer-Encoding: chunked\r\n"+
			"\r\n"+
			"0a\r\n"+
			"Body here\n\r\n"+
			"09\r\n"+
			"continued\r\n"+
			"0\r\n"+
			"\r\n")), nil)
	if err != nil {
		t.Fatal(err)
	}
	want := "Body here\ncontinued"
	buf := make([]byte, len(want))
	n, err := res.Body.Read(buf)
	if n != len(want) || err != io.EOF {
		t.Logf("body = %#v", res.Body)
		t.Errorf("Read = %v, %v; want %d, EOF", n, err, len(want))
	}
	if string(buf) != want {
		t.Errorf("buf = %q; want %q", buf, want)
	}
}

func TestDetectInMemoryReaders(t *testing.T) {
	pr, _ := io.Pipe()
	tests := []struct {
		r    io.Reader
		want bool
	}{
		{pr, false},

		{bytes.NewReader(nil), true},
		{bytes.NewBuffer(nil), true},
		{strings.NewReader(""), true},

		{io.NopCloser(pr), false},

		{io.NopCloser(bytes.NewReader(nil)), true},
		{io.NopCloser(bytes.NewBuffer(nil)), true},
		{io.NopCloser(strings.NewReader("")), true},
	}
	for i, tt := range tests {
		got := isKnownInMemoryReader(tt.r)
		if got != tt.want {
			t.Errorf("%d: got = %v; want %v", i, got, tt.want)
		}
	}
}

type mockTransferWriter struct {
	CalledReader io.Reader
	WriteCalled  bool
}

var _ io.ReaderFrom = (*mockTransferWriter)(nil)

func (w *mockTransferWriter) ReadFrom(r io.Reader) (int64, error) {
	w.CalledReader = r
	return io.Copy(io.Discard, r)
}

func (w *mockTransferWriter) Write(p []byte) (int, error) {
	w.WriteCalled = true
	return io.Discard.Write(p)
}

func TestTransferWriterWriteBodyReaderTypes(t *testing.T) {
	fileType := reflect.TypeOf(&os.File{})
	bufferType := reflect.TypeOf(&bytes.Buffer{})

	nBytes := int64(1 << 10)
	newFileFunc := func() (r io.Reader, done func(), err error) {
		f, err := os.CreateTemp("", "net-http-newfilefunc")
		if err != nil {
			return nil, nil, err
		}

		// Write some bytes to the file to enable reading.
		if _, err := io.CopyN(f, rand.Reader, nBytes); err != nil {
			return nil, nil, fmt.Errorf("failed to write data to file: %v", err)
		}
		if _, err := f.Seek(0, 0); err != nil {
			return nil, nil, fmt.Errorf("failed to seek to front: %v", err)
		}

		done = func() {
			f.Close()
			os.Remove(f.Name())
		}

		return f, done, nil
	}

	newBufferFunc := func() (io.Reader, func(), error) {
		return bytes.NewBuffer(make([]byte, nBytes)), func() {}, nil
	}

	cases := []struct {
		name             string
		bodyFunc         func() (io.Reader, func(), error)
		method           string
		contentLength    int64
		transferEncoding []string
		limitedReader    bool
		expectedReader   reflect.Type
		expectedWrite    bool
	}{
		{
			name:           "file, non-chunked, size set",
			bodyFunc:       newFileFunc,
			method:         "PUT",
			contentLength:  nBytes,
			limitedReader:  true,
			expectedReader: fileType,
		},
		{
			name:   "file, non-chunked, size set, nopCloser wrapped",
			method: "PUT",
			bodyFunc: func() (io.Reader, func(), error) {
				r, cleanup, err := newFileFunc()
				return io.NopCloser(r), cleanup, err
			},
			contentLength:  nBytes,
			limitedReader:  true,
			expectedReader: fileType,
		},
		{
			name:           "file, non-chunked, negative size",
			method:         "PUT",
			bodyFunc:       newFileFunc,
			contentLength:  -1,
			expectedReader: fileType,
		},
		{
			name:           "file, non-chunked, CONNECT, negative size",
			method:         "CONNECT",
			bodyFunc:       newFileFunc,
			contentLength:  -1,
			expectedReader: fileType,
		},
		{
			name:             "file, chunked",
			method:           "PUT",
			bodyFunc:         newFileFunc,
			transferEncoding: []string{"chunked"},
			expectedWrite:    true,
		},
		{
			name:           "buffer, non-chunked, size set",
			bodyFunc:       newBufferFunc,
			method:         "PUT",
			contentLength:  nBytes,
			limitedReader:  true,
			expectedReader: bufferType,
		},
		{
			name:   "buffer, non-chunked, size set, nopCloser wrapped",
			method: "PUT",
			bodyFunc: func() (io.Reader, func(), error) {
				r, cleanup, err := newBufferFunc()
				return io.NopCloser(r), cleanup, err
			},
			contentLength:  nBytes,
			limitedReader:  true,
			expectedReader: bufferType,
		},
		{
			name:          "buffer, non-chunked, negative size",
			method:        "PUT",
			bodyFunc:      newBufferFunc,
			contentLength: -1,
			expectedWrite: true,
		},
		{
			name:          "buffer, non-chunked, CONNECT, negative size",
			method:        "CONNECT",
			bodyFunc:      newBufferFunc,
			contentLength: -1,
			expectedWrite: true,
		},
		{
			name:             "buffer, chunked",
			method:           "PUT",
			bodyFunc:         newBufferFunc,
			transferEncoding: []string{"chunked"},
			expectedWrite:    true,
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			body, cleanup, err := tc.bodyFunc()
			if err != nil {
				t.Fatal(err)
			}
			defer cleanup()

			mw := &mockTransferWriter{}
			tw := &transferWriter{
				Body:             body,
				ContentLength:    tc.contentLength,
				TransferEncoding: tc.transferEncoding,
			}

			if err := tw.writeBody(mw, nil); err != nil {
				t.Fatal(err)
			}

			if tc.expectedReader != nil {
				if mw.CalledReader == nil {
					t.Fatal("did not call ReadFrom")
				}

				var actualReader reflect.Type
				lr, ok := mw.CalledReader.(*io.LimitedReader)
				if ok && tc.limitedReader {
					actualReader = reflect.TypeOf(lr.R)
				} else {
					actualReader = reflect.TypeOf(mw.CalledReader)
				}

				if tc.expectedReader != actualReader {
					t.Fatalf("got reader %s want %s", actualReader, tc.expectedReader)
				}
			}

			if tc.expectedWrite && !mw.WriteCalled {
				t.Fatal("did not invoke Write")
			}
		})
	}
}

func TestParseTransferEncoding(t *testing.T) {
	tests := []struct {
		hdr     http.Header
		wantErr error
	}{
		{
			hdr:     http.Header{"Transfer-Encoding": {"fugazi"}},
			wantErr: &unsupportedTEError{`unsupported transfer encoding: "fugazi"`},
		},
		{
			hdr:     http.Header{"Transfer-Encoding": {"chunked, chunked", "identity", "chunked"}},
			wantErr: &unsupportedTEError{`too many transfer encodings: ["chunked, chunked" "identity" "chunked"]`},
		},
		{
			hdr:     http.Header{"Transfer-Encoding": {""}},
			wantErr: &unsupportedTEError{`unsupported transfer encoding: ""`},
		},
		{
			hdr:     http.Header{"Transfer-Encoding": {"chunked, identity"}},
			wantErr: &unsupportedTEError{`unsupported transfer encoding: "chunked, identity"`},
		},
		{
			hdr:     http.Header{"Transfer-Encoding": {"chunked", "identity"}},
			wantErr: &unsupportedTEError{`too many transfer encodings: ["chunked" "identity"]`},
		},
		{
			hdr:     http.Header{"Transfer-Encoding": {"\x0bchunked"}},
			wantErr: &unsupportedTEError{`unsupported transfer encoding: "\vchunked"`},
		},
		{
			hdr:     http.Header{"Transfer-Encoding": {"chunked"}},
			wantErr: nil,
		},
	}

	for i, tt := range tests {
		tr := &transferReader{
			Header:     tt.hdr,
			ProtoMajor: 1,
			ProtoMinor: 1,
		}
		gotErr := tr.parseTransferEncoding()
		if !reflect.DeepEqual(gotErr, tt.wantErr) {
			t.Errorf("%d.\ngot error:\n%v\nwant error:\n%v\n\n", i, gotErr, tt.wantErr)
		}
	}
}

// issue 39017 - disallow Content-Length values such as "+3"
func TestParseContentLength(t *testing.T) {
	tests := []struct {
		cl      string
		wantErr error
	}{
		{
			cl:      "3",
			wantErr: nil,
		},
		{
			cl:      "+3",
			wantErr: badStringError("bad Content-Length", "+3"),
		},
		{
			cl:      "-3",
			wantErr: badStringError("bad Content-Length", "-3"),
		},
		{
			// max int64, for safe conversion before returning
			cl:      "9223372036854775807",
			wantErr: nil,
		},
		{
			cl:      "9223372036854775808",
			wantErr: badStringError("bad Content-Length", "9223372036854775808"),
		},
	}

	for _, tt := range tests {
		if _, gotErr := parseContentLength(tt.cl); !reflect.DeepEqual(gotErr, tt.wantErr) {
			t.Errorf("%q:\n\tgot=%v\n\twant=%v", tt.cl, gotErr, tt.wantErr)
		}
	}
}
<|endoftext|>