<commit_before>package brands

import (
	"encoding/json"
	"fmt"
	"github.com/Financial-Times/neo-utils-go/neoutils"
	"github.com/jmcvetta/neoism"
)

//service maintains info about runners and index managers
type service struct {
	cypherRunner neoutils.CypherRunner
	indexManager neoutils.IndexManager
}

// NewCypherBrandsService provides functions for create, update, delete operations on brands in Neo4j,
// plus other utility functions needed for a service
func NewCypherBrandsService(cypherRunner neoutils.CypherRunner, indexManager neoutils.IndexManager) service {
	return service{cypherRunner, indexManager}
}

//Initialise the driver
func (s service) Initialise() error {
	return neoutils.EnsureConstraints(s.indexManager, map[string]string{
		"Thing":         "uuid",
		"Concept":       "uuid",
		"Brand":         "uuid",
		"TMEIdentifier": "value",
		"UPPIdentifier": "value"})
}

func (s service) Read(uuid string) (interface{}, bool, error) {
	results := []struct {
		Brand
	}{}
	query := &neoism.CypherQuery{
		Statement: `
                        MATCH (n:Brand {uuid:{uuid}})
                        OPTIONAL MATCH (n)-[:HAS_PARENT]->(p:Thing)
                        OPTIONAL MATCH (upp:UPPIdentifier)-[:IDENTIFIES]->(n)
			OPTIONAL MATCH (tme:TMEIdentifier)-[:IDENTIFIES]->(n)
                        RETURN n.uuid AS uuid, n.prefLabel AS prefLabel,
                                n.strapline AS strapline, p.uuid as parentUUID,
                                n.descriptionXML AS descriptionXML,
                                n.description AS description, n.imageUrl AS _imageUrl,
                                {uuids:collect(distinct upp.value), TME:collect(distinct tme.value)} as alternativeIdentifiers,
                                labels(n) as types
                                `,
		Parameters: map[string]interface{}{
			"uuid": uuid,
		},
		Result: &results,
	}
	err := s.cypherRunner.CypherBatch([]*neoism.CypherQuery{query})
	if err != nil {
		return Brand{}, false, err
	}
	if len(results) == 0 {
		return Brand{}, false, nil
	}
	return results[0].Brand, true, nil
}

func (s service) Write(thing interface{}) error {
	brand := thing.(Brand)
	brandProps := map[string]string{
		"uuid":           brand.UUID,
		"prefLabel":      brand.PrefLabel,
		"strapline":      brand.Strapline,
		"descriptionXML": brand.DescriptionXML,
		"description":    brand.Description,
		"imageUrl":       brand.ImageURL,
	}

	deleteParentRelationship := &neoism.CypherQuery{
		Statement: `
                        MATCH (:Thing {uuid:{uuid}})-[r:HAS_PARENT]->(:Thing)
                        DELETE r`,
		Parameters: neoism.Props{
			"uuid": brand.UUID,
		},
	}

	deleteIdentifiers := &neoism.CypherQuery{
		Statement: `
                        MATCH (t:Thing {uuid:{uuid}})
                        OPTIONAL MATCH (i:Identifier)-[ir:IDENTIFIES]->(t)
                        DELETE ir, i`,
		Parameters: neoism.Props{
			"uuid": brand.UUID,
		},
	}

	writeBrand := &neoism.CypherQuery{
		Statement: `
                        MERGE (n:Thing {uuid: {uuid}})
                        SET n:Brand
                        SET n:Concept
			SET n:Classification
                        SET n={props}`,
		Parameters: neoism.Props{
			"uuid":  brand.UUID,
			"props": brandProps,
		},
	}
	queries := []*neoism.CypherQuery{deleteParentRelationship, deleteIdentifiers, writeBrand}

	if len(brand.ParentUUID) > 0 {
		fmt.Printf("**HAS PARENT %s", brand.ParentUUID)
		writeParent := &neoism.CypherQuery{
			Statement: `
                                MATCH (t:Thing {uuid:{uuid}})
                                MERGE (p:Thing {uuid:{parentUUID}})
                                MERGE (t)-[:HAS_PARENT]->(p)`,
			Parameters: neoism.Props{
				"parentUUID": brand.ParentUUID,
				"uuid":       brand.UUID,
			},
		}
		queries = append(queries, writeParent)
	}

	//ADD all the IDENTIFIER nodes and IDENTIFIES relationships
	for _, alternativeUUID := range brand.AlternativeIdentifiers.TME {
		alternativeIdentifierQuery := createNewIdentifierQuery(brand.UUID, tmeIdentifierLabel, alternativeUUID)
		queries = append(queries, alternativeIdentifierQuery)
	}

	for _, alternativeUUID := range brand.AlternativeIdentifiers.UUIDS {
		alternativeIdentifierQuery := createNewIdentifierQuery(brand.UUID, uppIdentifierLabel, alternativeUUID)
		queries = append(queries, alternativeIdentifierQuery)
	}

	return s.cypherRunner.CypherBatch(queries)
}

func createNewIdentifierQuery(uuid string, identifierLabel string, identifierValue string) *neoism.CypherQuery {
	statementTemplate := fmt.Sprintf(`MERGE (t:Thing {uuid:{uuid}})
					CREATE (i:Identifier {value:{value}})
					MERGE (t)<-[:IDENTIFIES]-(i)
					set i : %s `, identifierLabel)
	query := &neoism.CypherQuery{
		Statement: statementTemplate,
		Parameters: map[string]interface{}{
			"uuid":  uuid,
			"value": identifierValue,
		},
	}
	return query
}

func (s service) Delete(uuid string) (bool, error) {
	deleteIdentifiers := &neoism.CypherQuery{
		Statement: `MATCH (t:Thing {uuid:{uuid}})
                                OPTIONAL MATCH (i:Identifier)-[ir:IDENTIFIES]->(t)
                                WITH i, count(ir) as c, ir, t
                                WHERE c = 1
                                DELETE ir, i
                                `,
		Parameters: map[string]interface{}{
			"uuid": uuid,
		},
	}

	deleteOwnedRelationships := &neoism.CypherQuery{
		Statement: `
                        MATCH (n:Thing {uuid: {uuid}})-[p:HAS_PARENT]->(t:Thing)
                        DELETE p
                `,
		Parameters: neoism.Props{
			"uuid": uuid,
		},
		IncludeStats: true,
	}

	clearNode := &neoism.CypherQuery{
		Statement: `
			MATCH (n:Thing {uuid: {uuid}})
			REMOVE n:Brand:Concept:Classification
			SET n={props}
		`,
		Parameters: neoism.Props{
			"uuid": uuid,
			"props": neoism.Props{
				"uuid": uuid,
			},
		},
		IncludeStats: true,
	}

	removeNodeIfUnused := &neoism.CypherQuery{
		Statement: `
			MATCH (p:Thing {uuid: {uuid}})
			OPTIONAL MATCH (p)-[a]-(x)
			WITH p, count(a) AS relCount
			WHERE relCount = 0
			DELETE p
		`,
		Parameters: neoism.Props{
			"uuid": uuid,
		},
	}

	err := s.cypherRunner.CypherBatch([]*neoism.CypherQuery{deleteIdentifiers, deleteOwnedRelationships, clearNode, removeNodeIfUnused})

	s1, err := clearNode.Stats()
	if err != nil {
		return false, err
	}

	var deleted bool
	if s1.ContainsUpdates && s1.LabelsRemoved > 0 {
		deleted = true
	}

	return deleted, err
}

func (s service) DecodeJSON(dec *json.Decoder) (interface{}, string, error) {
	brand := Brand{}
	err := dec.Decode(&brand)
	return brand, brand.UUID, err
}

func (s service) Check() error {
	return neoutils.Check(s.cypherRunner)
}

func (s service) Count() (int, error) {

	results := []struct {
		Count int `json:"c"`
	}{}

	query := &neoism.CypherQuery{
		Statement: `MATCH (n:Brand) return count(n) as c`,
		Result:    &results,
	}

	err := s.cypherRunner.CypherBatch([]*neoism.CypherQuery{query})

	if err != nil {
		return 0, err
	}

	return results[0].Count, nil
}
<commit_msg>Ensure that there is an index on the Identifier value.<commit_after>package brands

import (
	"encoding/json"
	"fmt"
	"github.com/Financial-Times/neo-utils-go/neoutils"
	"github.com/jmcvetta/neoism"
)

//service maintains info about runners and index managers
type service struct {
	cypherRunner neoutils.CypherRunner
	indexManager neoutils.IndexManager
}

// NewCypherBrandsService provides functions for create, update, delete operations on brands in Neo4j,
// plus other utility functions needed for a service
func NewCypherBrandsService(cypherRunner neoutils.CypherRunner, indexManager neoutils.IndexManager) service {
	return service{cypherRunner, indexManager}
}

//Initialise the driver
func (s service) Initialise() error {

	err := neoutils.EnsureIndexes(s.indexManager,  map[string]string{
		"Identifier": "value",
	})

	if err != nil {
		return err
	}

	return neoutils.EnsureConstraints(s.indexManager, map[string]string{
		"Thing":         "uuid",
		"Concept":       "uuid",
		"Brand":         "uuid",
		"TMEIdentifier": "value",
		"UPPIdentifier": "value"})
}

func (s service) Read(uuid string) (interface{}, bool, error) {
	results := []struct {
		Brand
	}{}
	query := &neoism.CypherQuery{
		Statement: `
                        MATCH (n:Brand {uuid:{uuid}})
                        OPTIONAL MATCH (n)-[:HAS_PARENT]->(p:Thing)
                        OPTIONAL MATCH (upp:UPPIdentifier)-[:IDENTIFIES]->(n)
			OPTIONAL MATCH (tme:TMEIdentifier)-[:IDENTIFIES]->(n)
                        RETURN n.uuid AS uuid, n.prefLabel AS prefLabel,
                                n.strapline AS strapline, p.uuid as parentUUID,
                                n.descriptionXML AS descriptionXML,
                                n.description AS description, n.imageUrl AS _imageUrl,
                                {uuids:collect(distinct upp.value), TME:collect(distinct tme.value)} as alternativeIdentifiers,
                                labels(n) as types
                                `,
		Parameters: map[string]interface{}{
			"uuid": uuid,
		},
		Result: &results,
	}
	err := s.cypherRunner.CypherBatch([]*neoism.CypherQuery{query})
	if err != nil {
		return Brand{}, false, err
	}
	if len(results) == 0 {
		return Brand{}, false, nil
	}
	return results[0].Brand, true, nil
}

func (s service) Write(thing interface{}) error {
	brand := thing.(Brand)
	brandProps := map[string]string{
		"uuid":           brand.UUID,
		"prefLabel":      brand.PrefLabel,
		"strapline":      brand.Strapline,
		"descriptionXML": brand.DescriptionXML,
		"description":    brand.Description,
		"imageUrl":       brand.ImageURL,
	}

	deleteParentRelationship := &neoism.CypherQuery{
		Statement: `
                        MATCH (:Thing {uuid:{uuid}})-[r:HAS_PARENT]->(:Thing)
                        DELETE r`,
		Parameters: neoism.Props{
			"uuid": brand.UUID,
		},
	}

	deleteIdentifiers := &neoism.CypherQuery{
		Statement: `
                        MATCH (t:Thing {uuid:{uuid}})
                        OPTIONAL MATCH (i:Identifier)-[ir:IDENTIFIES]->(t)
                        DELETE ir, i`,
		Parameters: neoism.Props{
			"uuid": brand.UUID,
		},
	}

	writeBrand := &neoism.CypherQuery{
		Statement: `
                        MERGE (n:Thing {uuid: {uuid}})
                        SET n:Brand
                        SET n:Concept
			SET n:Classification
                        SET n={props}`,
		Parameters: neoism.Props{
			"uuid":  brand.UUID,
			"props": brandProps,
		},
	}
	queries := []*neoism.CypherQuery{deleteParentRelationship, deleteIdentifiers, writeBrand}

	if len(brand.ParentUUID) > 0 {
		fmt.Printf("**HAS PARENT %s", brand.ParentUUID)
		writeParent := &neoism.CypherQuery{
			Statement: `
                                MATCH (t:Thing {uuid:{uuid}})
                                MERGE (p:Thing {uuid:{parentUUID}})
                                MERGE (t)-[:HAS_PARENT]->(p)`,
			Parameters: neoism.Props{
				"parentUUID": brand.ParentUUID,
				"uuid":       brand.UUID,
			},
		}
		queries = append(queries, writeParent)
	}

	//ADD all the IDENTIFIER nodes and IDENTIFIES relationships
	for _, alternativeUUID := range brand.AlternativeIdentifiers.TME {
		alternativeIdentifierQuery := createNewIdentifierQuery(brand.UUID, tmeIdentifierLabel, alternativeUUID)
		queries = append(queries, alternativeIdentifierQuery)
	}

	for _, alternativeUUID := range brand.AlternativeIdentifiers.UUIDS {
		alternativeIdentifierQuery := createNewIdentifierQuery(brand.UUID, uppIdentifierLabel, alternativeUUID)
		queries = append(queries, alternativeIdentifierQuery)
	}

	return s.cypherRunner.CypherBatch(queries)
}

func createNewIdentifierQuery(uuid string, identifierLabel string, identifierValue string) *neoism.CypherQuery {
	statementTemplate := fmt.Sprintf(`MERGE (t:Thing {uuid:{uuid}})
					CREATE (i:Identifier {value:{value}})
					MERGE (t)<-[:IDENTIFIES]-(i)
					set i : %s `, identifierLabel)
	query := &neoism.CypherQuery{
		Statement: statementTemplate,
		Parameters: map[string]interface{}{
			"uuid":  uuid,
			"value": identifierValue,
		},
	}
	return query
}

func (s service) Delete(uuid string) (bool, error) {
	deleteIdentifiers := &neoism.CypherQuery{
		Statement: `MATCH (t:Thing {uuid:{uuid}})
                                OPTIONAL MATCH (i:Identifier)-[ir:IDENTIFIES]->(t)
                                WITH i, count(ir) as c, ir, t
                                WHERE c = 1
                                DELETE ir, i
                                `,
		Parameters: map[string]interface{}{
			"uuid": uuid,
		},
	}

	deleteOwnedRelationships := &neoism.CypherQuery{
		Statement: `
                        MATCH (n:Thing {uuid: {uuid}})-[p:HAS_PARENT]->(t:Thing)
                        DELETE p
                `,
		Parameters: neoism.Props{
			"uuid": uuid,
		},
		IncludeStats: true,
	}

	clearNode := &neoism.CypherQuery{
		Statement: `
			MATCH (n:Thing {uuid: {uuid}})
			REMOVE n:Brand:Concept:Classification
			SET n={props}
		`,
		Parameters: neoism.Props{
			"uuid": uuid,
			"props": neoism.Props{
				"uuid": uuid,
			},
		},
		IncludeStats: true,
	}

	removeNodeIfUnused := &neoism.CypherQuery{
		Statement: `
			MATCH (p:Thing {uuid: {uuid}})
			OPTIONAL MATCH (p)-[a]-(x)
			WITH p, count(a) AS relCount
			WHERE relCount = 0
			DELETE p
		`,
		Parameters: neoism.Props{
			"uuid": uuid,
		},
	}

	err := s.cypherRunner.CypherBatch([]*neoism.CypherQuery{deleteIdentifiers, deleteOwnedRelationships, clearNode, removeNodeIfUnused})

	s1, err := clearNode.Stats()
	if err != nil {
		return false, err
	}

	var deleted bool
	if s1.ContainsUpdates && s1.LabelsRemoved > 0 {
		deleted = true
	}

	return deleted, err
}

func (s service) DecodeJSON(dec *json.Decoder) (interface{}, string, error) {
	brand := Brand{}
	err := dec.Decode(&brand)
	return brand, brand.UUID, err
}

func (s service) Check() error {
	return neoutils.Check(s.cypherRunner)
}

func (s service) Count() (int, error) {

	results := []struct {
		Count int `json:"c"`
	}{}

	query := &neoism.CypherQuery{
		Statement: `MATCH (n:Brand) return count(n) as c`,
		Result:    &results,
	}

	err := s.cypherRunner.CypherBatch([]*neoism.CypherQuery{query})

	if err != nil {
		return 0, err
	}

	return results[0].Count, nil
}
<|endoftext|>


<commit_before>package broadcaster

import (
	"encoding/json"
	"fmt"
	"github.com/gorilla/websocket"
	"net/http"
	"time"
)

type Transport interface {
	fmt.Stringer
	Init(close chan<- struct{})
	Close()
	Ping() error
	ReadLoop()
	Send(message *QueuedMessage) error
}

type WebSocketTransport struct {
	sock               *websocket.Conn
	close              chan<- struct{}
	writeDeadlineDelay time.Duration
}

func NewWebSocketTransport(
	sock *websocket.Conn,
	writeDeadlineDelay time.Duration,
) Transport {
	return &WebSocketTransport{
		sock:               sock,
		writeDeadlineDelay: writeDeadlineDelay,
	}
}

func (t *WebSocketTransport) String() string {
	return "WebSocket"
}

func (t *WebSocketTransport) Close() {
	t.sock.WriteControl(
		websocket.CloseMessage,
		[]byte{},
		t.writeDeadline(),
	)
}

func (t *WebSocketTransport) Init(close chan<- struct{}) {
	t.close = close
}

func (t *WebSocketTransport) Ping() error {
	return t.sock.WriteControl(
		websocket.PingMessage,
		[]byte{},
		t.writeDeadline(),
	)
}

func (t *WebSocketTransport) ReadLoop() {
	for {
		_, _, err := t.sock.ReadMessage()
		if err != nil {
			break
		}
	}
	close(t.close)
}

func (t *WebSocketTransport) Send(message *QueuedMessage) error {
	defer message.Dispatched()
	t.sock.SetWriteDeadline(t.writeDeadline())
	return t.sock.WriteJSON(*message)
}

func (t *WebSocketTransport) writeDeadline() time.Time {
	return time.Now().Add(t.writeDeadlineDelay)
}

type SSETransport struct {
	w     http.ResponseWriter
	close chan<- struct{}
}

func NewSSETransport(w http.ResponseWriter) Transport {
	return &SSETransport{
		w: w,
	}
}

func (t *SSETransport) String() string {
	return "SSE"
}

func (t *SSETransport) Close() {
	close(t.close)
}

func (t *SSETransport) Init(close chan<- struct{}) {
	t.w.Header().Set("Content-Type", "text/event-stream")
	t.w.Header().Set("X-Accel-Buffering", "no")
	t.w.WriteHeader(http.StatusOK)
	if flusher, ok := t.w.(http.Flusher); ok {
		flusher.Flush()
	}
	t.close = close
}

func (t *SSETransport) Ping() error {
	if _, err := t.w.Write([]byte(":\n")); err != nil {
		return err
	}
	if flusher, ok := t.w.(http.Flusher); ok {
		flusher.Flush()
	}
	return nil
}

func (t *SSETransport) ReadLoop() {
	closeNotifier, ok := t.w.(http.CloseNotifier)
	if !ok {
		return
	}
	<-closeNotifier.CloseNotify()
	close(t.close)
}

func (t *SSETransport) Send(message *QueuedMessage) error {
	defer message.Dispatched()
	msg, err := json.Marshal(*message)
	if err != nil {
		return err
	}
	_, err = fmt.Fprintf(t.w, "data: %s\n\n", string(msg))
	if err != nil {
		return err
	}
	if flusher, ok := t.w.(http.Flusher); ok {
		flusher.Flush()
	}
	return nil
}
<commit_msg>Send the actual message instead of the wrapper<commit_after>package broadcaster

import (
	"fmt"
	"github.com/gorilla/websocket"
	"net/http"
	"time"
)

type Transport interface {
	fmt.Stringer
	Init(close chan<- struct{})
	Close()
	Ping() error
	ReadLoop()
	Send(message *QueuedMessage) error
}

type WebSocketTransport struct {
	sock               *websocket.Conn
	close              chan<- struct{}
	writeDeadlineDelay time.Duration
}

func NewWebSocketTransport(
	sock *websocket.Conn,
	writeDeadlineDelay time.Duration,
) Transport {
	return &WebSocketTransport{
		sock:               sock,
		writeDeadlineDelay: writeDeadlineDelay,
	}
}

func (t *WebSocketTransport) String() string {
	return "WebSocket"
}

func (t *WebSocketTransport) Close() {
	t.sock.WriteControl(
		websocket.CloseMessage,
		[]byte{},
		t.writeDeadline(),
	)
}

func (t *WebSocketTransport) Init(close chan<- struct{}) {
	t.close = close
}

func (t *WebSocketTransport) Ping() error {
	return t.sock.WriteControl(
		websocket.PingMessage,
		[]byte{},
		t.writeDeadline(),
	)
}

func (t *WebSocketTransport) ReadLoop() {
	for {
		_, _, err := t.sock.ReadMessage()
		if err != nil {
			break
		}
	}
	close(t.close)
}

func (t *WebSocketTransport) Send(message *QueuedMessage) error {
	defer message.Dispatched()
	t.sock.SetWriteDeadline(t.writeDeadline())
	return t.sock.WriteMessage(
		websocket.TextMessage,
		[]byte(message.message.Message),
	)
}

func (t *WebSocketTransport) writeDeadline() time.Time {
	return time.Now().Add(t.writeDeadlineDelay)
}

type SSETransport struct {
	w     http.ResponseWriter
	close chan<- struct{}
}

func NewSSETransport(w http.ResponseWriter) Transport {
	return &SSETransport{
		w: w,
	}
}

func (t *SSETransport) String() string {
	return "SSE"
}

func (t *SSETransport) Close() {
	close(t.close)
}

func (t *SSETransport) Init(close chan<- struct{}) {
	t.w.Header().Set("Content-Type", "text/event-stream")
	t.w.Header().Set("X-Accel-Buffering", "no")
	t.w.WriteHeader(http.StatusOK)
	if flusher, ok := t.w.(http.Flusher); ok {
		flusher.Flush()
	}
	t.close = close
}

func (t *SSETransport) Ping() error {
	if _, err := t.w.Write([]byte(":\n")); err != nil {
		return err
	}
	if flusher, ok := t.w.(http.Flusher); ok {
		flusher.Flush()
	}
	return nil
}

func (t *SSETransport) ReadLoop() {
	closeNotifier, ok := t.w.(http.CloseNotifier)
	if !ok {
		return
	}
	<-closeNotifier.CloseNotify()
	close(t.close)
}

func (t *SSETransport) Send(message *QueuedMessage) error {
	defer message.Dispatched()
	_, err := fmt.Fprintf(t.w, "data: %s\n\n", message.message.Message)
	if err != nil {
		return err
	}
	if flusher, ok := t.w.(http.Flusher); ok {
		flusher.Flush()
	}
	return nil
}
<|endoftext|>


<commit_before>package broker

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"strings"
	"testing"
	"time"

	glog "github.com/golang/glog"
	jsonpb "github.com/golang/protobuf/jsonpb"
	proto "github.com/golang/protobuf/proto"
	emulators "google/emulators"
)

// Test-only client.
type httpJsonClient struct {
	port   int
	client http.Client
}

func (c *httpJsonClient) get(url string, output proto.Message) error {
	resp, err := http.Get(url)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return errors.New("Request failed: " + resp.Status)
	}
	if output != nil {
		err = jsonpb.Unmarshal(resp.Body, output)
		if err != nil {
			return err
		}
	}
	return nil
}

func (c *httpJsonClient) post(url string, req proto.Message, output proto.Message) error {
	return c.do("POST", url, req, output)
}

// req, err := http.NewRequest("PATCH", url, strings.NewReader(body))
// if err != nil {
//   return nil, err
// }
// resp, err := c.client.Do(req)

func (c *httpJsonClient) patch(url string, req proto.Message, output proto.Message) error {
	return c.do("PATCH", url, req, output)
}

func (c *httpJsonClient) do(method string, url string, req proto.Message, output proto.Message) error {
	var reqBody io.Reader = nil
	if req != nil {
		m := jsonpb.Marshaler{}
		body, err := m.MarshalToString(req)
		if err != nil {
			return nil
		}
		glog.Infof("Making %s request to %s with body: %s", method, url, body)
		reqBody = strings.NewReader(body)
	}
	httpReq, err := http.NewRequest(method, url, reqBody)
	if err != nil {
		return err
	}
	resp, err := c.client.Do(httpReq)
	if err != nil {
		return nil
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		b, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return errors.New("Request failed: " + resp.Status)
		}
		return fmt.Errorf("Request failed: %v: %s", resp.Status, b)
	}
	if output != nil {
		err = jsonpb.Unmarshal(resp.Body, output)
		if err != nil {
			return err
		}
	}
	return nil
}

// Waits until the HTTP server is ready. Requests sent before the server is
// ready may result in a variety of error responses (4xx and 5xx).
func (c *httpJsonClient) awaitReady(timeout time.Duration) error {
	deadline := time.Now().Add(timeout)
	for time.Now().Before(deadline) {
		_, err := c.listEmulators()
		if err == nil {
			return nil
		}
		time.Sleep(100 * time.Millisecond)
	}
	return errors.New("Timed-out waiting for client to become ready!")
}

func (c *httpJsonClient) createEmulator(emu *emulators.Emulator) error {
	url := fmt.Sprintf("http://localhost:%d/v1/emulators", c.port)
	return c.post(url, emu, EmptyPb)
}

func (c *httpJsonClient) getEmulator(id string) (*emulators.Emulator, error) {
	url := fmt.Sprintf("http://localhost:%d/v1/emulators/%s", c.port, id)
	emu := &emulators.Emulator{}
	err := c.get(url, emu)
	return emu, err
}

func (c *httpJsonClient) listEmulators() (*emulators.ListEmulatorsResponse, error) {
	url := fmt.Sprintf("http://localhost:%d/v1/emulators", c.port)
	listResp := &emulators.ListEmulatorsResponse{}
	err := c.get(url, listResp)
	return listResp, err
}

func (c *httpJsonClient) startEmulator(id string) error {
	url := fmt.Sprintf("http://localhost:%d/v1/emulators/%s:start", c.port, id)
	return c.post(url, nil, nil)
}

func (c *httpJsonClient) reportEmulatorOnline(report *emulators.ReportEmulatorOnlineRequest) error {
	url := fmt.Sprintf("http://localhost:%d/v1/emulators/%s:report_online", c.port, report.EmulatorId)
	return c.post(url, report, nil)
}

func (c *httpJsonClient) stopEmulator(id string) error {
	url := fmt.Sprintf("http://localhost:%d/v1/emulators/%s:stop", c.port, id)
	return c.post(url, nil, nil)
}

func (c *httpJsonClient) createResolveRule(rule *emulators.ResolveRule) error {
	url := fmt.Sprintf("http://localhost:%d/v1/resolve_rules", c.port)
	return c.post(url, rule, nil)
}

func (c *httpJsonClient) getResolveRule(id string) (*emulators.ResolveRule, error) {
	url := fmt.Sprintf("http://localhost:%d/v1/resolve_rules/%s", c.port, id)
	rule := &emulators.ResolveRule{}
	err := c.get(url, rule)
	return rule, err
}

func (c *httpJsonClient) listResolveRules() (*emulators.ListResolveRulesResponse, error) {
	url := fmt.Sprintf("http://localhost:%d/v1/resolve_rules", c.port)
	listResp := &emulators.ListResolveRulesResponse{}
	err := c.get(url, listResp)
	return listResp, err
}

func (c *httpJsonClient) updateResolveRule(rule *emulators.ResolveRule) (*emulators.ResolveRule, error) {
	url := fmt.Sprintf("http://localhost:%d/v1/resolve_rules/%s", c.port, rule.RuleId)
	updatedRule := &emulators.ResolveRule{}
	err := c.patch(url, rule, updatedRule)
	return updatedRule, err
}

func (c *httpJsonClient) resolve(req *emulators.ResolveRequest) (*emulators.ResolveResponse, error) {
	url := fmt.Sprintf("http://localhost:%d/v1/resolve_rules:resolve", c.port)
	resp := &emulators.ResolveResponse{}
	err := c.post(url, req, resp)
	return resp, err
}

// Sanity check HTTP/Json access to the emulators resource.
// Tests create, get, and list.
func TestHttpJson_EmulatorsResource(t *testing.T) {
	b, err := startNewBroker(brokerConfig)
	if err != nil {
		t.Fatal(err)
	}
	defer b.Shutdown()

	c := httpJsonClient{port: b.Port()}
	c.awaitReady(2 * time.Second)
	listResp, err := c.listEmulators()
	if err != nil {
		t.Fatal(err)
	}
	if len(listResp.Emulators) != 0 {
		t.Fatalf("Expected zero emulators: %v", listResp)
	}
	err = c.createEmulator(dummyEmulator)
	if err != nil {
		t.Fatal(err)
	}
	emu, err := c.getEmulator(dummyEmulator.EmulatorId)
	if !proto.Equal(emu, dummyEmulator) {
		t.Fatalf("Expected %v: %v", dummyEmulator, emu)
	}
	listResp, err = c.listEmulators()
	if err != nil {
		t.Fatal(err)
	}
	if len(listResp.Emulators) != 1 {
		t.Fatalf("Expected 1 emulator: %v", listResp)
	}
}

// Sanity check HTTP/Json access to the resolve_rules resource.
// Tests create, get, list, and update.
func TestHttpJson_ResolveRulesResource(t *testing.T) {
	b, err := startNewBroker(brokerConfig)
	if err != nil {
		t.Fatal(err)
	}
	defer b.Shutdown()

	c := httpJsonClient{port: b.Port()}
	c.awaitReady(2 * time.Second)
	listResp, err := c.listResolveRules()
	if err != nil {
		t.Fatal(err)
	}
	if len(listResp.Rules) != 0 {
		t.Fatalf("Expected zero rule: %v", listResp)
	}
	err = c.createResolveRule(dummyEmulator.Rule)
	if err != nil {
		t.Fatal(err)
	}
	rule, err := c.getResolveRule(dummyEmulator.Rule.RuleId)
	if err != nil {
		t.Fatal(err)
	}
	listResp, err = c.listResolveRules()
	if err != nil {
		t.Fatal(err)
	}
	if len(listResp.Rules) != 1 {
		t.Fatalf("Expected 1 rule: %v", listResp)
	}
	rule.TargetPatterns = append(rule.TargetPatterns, "new")
	updatedRule, err := c.updateResolveRule(rule)
	if err != nil {
		t.Fatal(err)
	}
	if !unorderedEqual(updatedRule.TargetPatterns, rule.TargetPatterns) {
		t.Fatalf("Expected %v: %v", rule.TargetPatterns, updatedRule.TargetPatterns)
	}
}

// Tests emulators:start, :report online, and :stop.
func TestHttpJson_EmulatorsCustomMethods(t *testing.T) {
	b, err := startNewBroker(brokerConfig)
	if err != nil {
		t.Fatal(err)
	}
	defer b.Shutdown()

	c := httpJsonClient{port: b.Port()}
	c.awaitReady(2 * time.Second)
	realNoReg := proto.Clone(realEmulator).(*emulators.Emulator)
	realNoReg.StartCommand.Args = []string{"--port={port:real}"}
	err = c.createEmulator(realNoReg)
	if err != nil {
		t.Fatal(err)
	}

	// Start the emulator, and wait until it is ready to be reported online.
	done := make(chan bool)
	go func() {
		err = c.startEmulator(realNoReg.EmulatorId)
		if err != nil {
			t.Fatal(err)
		}
		done <- true
	}()
	err = b.s.waitForStarting(realNoReg.EmulatorId, time.Now().Add(2*time.Second))
	if err != nil {
		t.Fatal(err)
	}

	// Report the emulator online.
	report := &emulators.ReportEmulatorOnlineRequest{
		EmulatorId:   realNoReg.EmulatorId,
		ResolvedHost: "foo"}
	err = c.reportEmulatorOnline(report)
	if err != nil {
		t.Fatal(err)
	}
	select {
	case <-time.After(5 * time.Second):
		t.Fatalf("Timed-out starting emulator!")
	case <-done:
		break
	}
	emu, err := c.getEmulator(realNoReg.EmulatorId)
	if err != nil {
		t.Fatal(err)
	}
	if emu.State != emulators.Emulator_ONLINE {
		t.Fatalf("Expected %v: %v", emulators.Emulator_ONLINE, emu.State)
	}

	// Stop the emulator.
	err = c.stopEmulator(realNoReg.EmulatorId)
	if err != nil {
		t.Fatal(err)
	}
	emu, err = c.getEmulator(realNoReg.EmulatorId)
	if err != nil {
		t.Fatal(err)
	}
	if emu.State != emulators.Emulator_OFFLINE {
		t.Fatalf("Expected %v: %v", emulators.Emulator_OFFLINE, emu.State)
	}
}

// Tests resolve_rules:resolve.
func TestHttpJson_Resolve(t *testing.T) {
	b, err := startNewBroker(brokerConfig)
	if err != nil {
		t.Fatal(err)
	}
	defer b.Shutdown()

	c := httpJsonClient{port: b.Port()}
	c.awaitReady(2 * time.Second)
	rule := &emulators.ResolveRule{RuleId: "r0", TargetPatterns: []string{"foo"}, ResolvedHost: "bar"}
	err = c.createResolveRule(rule)
	if err != nil {
		t.Fatal(err)
	}
	_, err = c.getResolveRule("r0")
	if err != nil {
		t.Fatal(err)
	}
	resolveResp, err := c.resolve(&emulators.ResolveRequest{Target: "foo"})
	if err != nil {
		t.Fatal(err)
	}
	if resolveResp.Target != "bar" {
		t.Fatalf("Expected bar: %v", resolveResp.Target)
	}
}
<commit_msg>Include the bad request message in the error.<commit_after>package broker

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"strings"
	"testing"
	"time"

	glog "github.com/golang/glog"
	jsonpb "github.com/golang/protobuf/jsonpb"
	proto "github.com/golang/protobuf/proto"
	emulators "google/emulators"
)

// Test-only client.
type httpJsonClient struct {
	port   int
	client http.Client
}

func (c *httpJsonClient) get(url string, output proto.Message) error {
	resp, err := http.Get(url)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return errors.New("Request failed: " + resp.Status)
	}
	if output != nil {
		err = jsonpb.Unmarshal(resp.Body, output)
		if err != nil {
			return err
		}
	}
	return nil
}

func (c *httpJsonClient) post(url string, req proto.Message, output proto.Message) error {
	return c.do("POST", url, req, output)
}

// req, err := http.NewRequest("PATCH", url, strings.NewReader(body))
// if err != nil {
//   return nil, err
// }
// resp, err := c.client.Do(req)

func (c *httpJsonClient) patch(url string, req proto.Message, output proto.Message) error {
	return c.do("PATCH", url, req, output)
}

func (c *httpJsonClient) do(method string, url string, req proto.Message, output proto.Message) error {
	var reqBody io.Reader = nil
	if req != nil {
		m := jsonpb.Marshaler{}
		body, err := m.MarshalToString(req)
		if err != nil {
			return nil
		}
		glog.Infof("Making %s request to %s with body: %s", method, url, body)
		reqBody = strings.NewReader(body)
	}
	httpReq, err := http.NewRequest(method, url, reqBody)
	if err != nil {
		return err
	}
	resp, err := c.client.Do(httpReq)
	if err != nil {
		return nil
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		b, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return fmt.Errorf("Request failed [%v]: %v", req, resp.Status)
		}
		return fmt.Errorf("Request failed [%v]: %v: %s", req, resp.Status, b)
	}
	if output != nil {
		err = jsonpb.Unmarshal(resp.Body, output)
		if err != nil {
			return err
		}
	}
	return nil
}

// Waits until the HTTP server is ready. Requests sent before the server is
// ready may result in a variety of error responses (4xx and 5xx).
func (c *httpJsonClient) awaitReady(timeout time.Duration) error {
	deadline := time.Now().Add(timeout)
	for time.Now().Before(deadline) {
		_, err := c.listEmulators()
		if err == nil {
			return nil
		}
		time.Sleep(100 * time.Millisecond)
	}
	return errors.New("Timed-out waiting for client to become ready!")
}

func (c *httpJsonClient) createEmulator(emu *emulators.Emulator) error {
	url := fmt.Sprintf("http://localhost:%d/v1/emulators", c.port)
	return c.post(url, emu, EmptyPb)
}

func (c *httpJsonClient) getEmulator(id string) (*emulators.Emulator, error) {
	url := fmt.Sprintf("http://localhost:%d/v1/emulators/%s", c.port, id)
	emu := &emulators.Emulator{}
	err := c.get(url, emu)
	return emu, err
}

func (c *httpJsonClient) listEmulators() (*emulators.ListEmulatorsResponse, error) {
	url := fmt.Sprintf("http://localhost:%d/v1/emulators", c.port)
	listResp := &emulators.ListEmulatorsResponse{}
	err := c.get(url, listResp)
	return listResp, err
}

func (c *httpJsonClient) startEmulator(id string) error {
	url := fmt.Sprintf("http://localhost:%d/v1/emulators/%s:start", c.port, id)
	return c.post(url, nil, nil)
}

func (c *httpJsonClient) reportEmulatorOnline(report *emulators.ReportEmulatorOnlineRequest) error {
	url := fmt.Sprintf("http://localhost:%d/v1/emulators/%s:report_online", c.port, report.EmulatorId)
	return c.post(url, report, nil)
}

func (c *httpJsonClient) stopEmulator(id string) error {
	url := fmt.Sprintf("http://localhost:%d/v1/emulators/%s:stop", c.port, id)
	return c.post(url, nil, nil)
}

func (c *httpJsonClient) createResolveRule(rule *emulators.ResolveRule) error {
	url := fmt.Sprintf("http://localhost:%d/v1/resolve_rules", c.port)
	return c.post(url, rule, nil)
}

func (c *httpJsonClient) getResolveRule(id string) (*emulators.ResolveRule, error) {
	url := fmt.Sprintf("http://localhost:%d/v1/resolve_rules/%s", c.port, id)
	rule := &emulators.ResolveRule{}
	err := c.get(url, rule)
	return rule, err
}

func (c *httpJsonClient) listResolveRules() (*emulators.ListResolveRulesResponse, error) {
	url := fmt.Sprintf("http://localhost:%d/v1/resolve_rules", c.port)
	listResp := &emulators.ListResolveRulesResponse{}
	err := c.get(url, listResp)
	return listResp, err
}

func (c *httpJsonClient) updateResolveRule(rule *emulators.ResolveRule) (*emulators.ResolveRule, error) {
	url := fmt.Sprintf("http://localhost:%d/v1/resolve_rules/%s", c.port, rule.RuleId)
	updatedRule := &emulators.ResolveRule{}
	err := c.patch(url, rule, updatedRule)
	return updatedRule, err
}

func (c *httpJsonClient) resolve(req *emulators.ResolveRequest) (*emulators.ResolveResponse, error) {
	url := fmt.Sprintf("http://localhost:%d/v1/resolve_rules:resolve", c.port)
	resp := &emulators.ResolveResponse{}
	err := c.post(url, req, resp)
	return resp, err
}

// Sanity check HTTP/Json access to the emulators resource.
// Tests create, get, and list.
func TestHttpJson_EmulatorsResource(t *testing.T) {
	b, err := startNewBroker(brokerConfig)
	if err != nil {
		t.Fatal(err)
	}
	defer b.Shutdown()

	c := httpJsonClient{port: b.Port()}
	c.awaitReady(2 * time.Second)
	listResp, err := c.listEmulators()
	if err != nil {
		t.Fatal(err)
	}
	if len(listResp.Emulators) != 0 {
		t.Fatalf("Expected zero emulators: %v", listResp)
	}
	err = c.createEmulator(dummyEmulator)
	if err != nil {
		t.Fatal(err)
	}
	emu, err := c.getEmulator(dummyEmulator.EmulatorId)
	if !proto.Equal(emu, dummyEmulator) {
		t.Fatalf("Expected %v: %v", dummyEmulator, emu)
	}
	listResp, err = c.listEmulators()
	if err != nil {
		t.Fatal(err)
	}
	if len(listResp.Emulators) != 1 {
		t.Fatalf("Expected 1 emulator: %v", listResp)
	}
}

// Sanity check HTTP/Json access to the resolve_rules resource.
// Tests create, get, list, and update.
func TestHttpJson_ResolveRulesResource(t *testing.T) {
	b, err := startNewBroker(brokerConfig)
	if err != nil {
		t.Fatal(err)
	}
	defer b.Shutdown()

	c := httpJsonClient{port: b.Port()}
	c.awaitReady(2 * time.Second)
	listResp, err := c.listResolveRules()
	if err != nil {
		t.Fatal(err)
	}
	if len(listResp.Rules) != 0 {
		t.Fatalf("Expected zero rule: %v", listResp)
	}
	err = c.createResolveRule(dummyEmulator.Rule)
	if err != nil {
		t.Fatal(err)
	}
	rule, err := c.getResolveRule(dummyEmulator.Rule.RuleId)
	if err != nil {
		t.Fatal(err)
	}
	listResp, err = c.listResolveRules()
	if err != nil {
		t.Fatal(err)
	}
	if len(listResp.Rules) != 1 {
		t.Fatalf("Expected 1 rule: %v", listResp)
	}
	rule.TargetPatterns = append(rule.TargetPatterns, "new")
	updatedRule, err := c.updateResolveRule(rule)
	if err != nil {
		t.Fatal(err)
	}
	if !unorderedEqual(updatedRule.TargetPatterns, rule.TargetPatterns) {
		t.Fatalf("Expected %v: %v", rule.TargetPatterns, updatedRule.TargetPatterns)
	}
}

// Tests emulators:start, :report online, and :stop.
func TestHttpJson_EmulatorsCustomMethods(t *testing.T) {
	b, err := startNewBroker(brokerConfig)
	if err != nil {
		t.Fatal(err)
	}
	defer b.Shutdown()

	c := httpJsonClient{port: b.Port()}
	c.awaitReady(2 * time.Second)
	realNoReg := proto.Clone(realEmulator).(*emulators.Emulator)
	realNoReg.StartCommand.Args = []string{"--port={port:real}"}
	err = c.createEmulator(realNoReg)
	if err != nil {
		t.Fatal(err)
	}

	// Start the emulator, and wait until it is ready to be reported online.
	done := make(chan bool)
	go func() {
		err = c.startEmulator(realNoReg.EmulatorId)
		if err != nil {
			t.Fatal(err)
		}
		done <- true
	}()
	err = b.s.waitForStarting(realNoReg.EmulatorId, time.Now().Add(2*time.Second))
	if err != nil {
		t.Fatal(err)
	}

	// Report the emulator online.
	report := &emulators.ReportEmulatorOnlineRequest{
		EmulatorId:   realNoReg.EmulatorId,
		ResolvedHost: "foo"}
	err = c.reportEmulatorOnline(report)
	if err != nil {
		t.Fatal(err)
	}
	select {
	case <-time.After(5 * time.Second):
		t.Fatalf("Timed-out starting emulator!")
	case <-done:
		break
	}
	emu, err := c.getEmulator(realNoReg.EmulatorId)
	if err != nil {
		t.Fatal(err)
	}
	if emu.State != emulators.Emulator_ONLINE {
		t.Fatalf("Expected %v: %v", emulators.Emulator_ONLINE, emu.State)
	}

	// Stop the emulator.
	err = c.stopEmulator(realNoReg.EmulatorId)
	if err != nil {
		t.Fatal(err)
	}
	emu, err = c.getEmulator(realNoReg.EmulatorId)
	if err != nil {
		t.Fatal(err)
	}
	if emu.State != emulators.Emulator_OFFLINE {
		t.Fatalf("Expected %v: %v", emulators.Emulator_OFFLINE, emu.State)
	}
}

// Tests resolve_rules:resolve.
func TestHttpJson_Resolve(t *testing.T) {
	b, err := startNewBroker(brokerConfig)
	if err != nil {
		t.Fatal(err)
	}
	defer b.Shutdown()

	c := httpJsonClient{port: b.Port()}
	c.awaitReady(2 * time.Second)
	rule := &emulators.ResolveRule{RuleId: "r0", TargetPatterns: []string{"foo"}, ResolvedHost: "bar"}
	err = c.createResolveRule(rule)
	if err != nil {
		t.Fatal(err)
	}
	_, err = c.getResolveRule("r0")
	if err != nil {
		t.Fatal(err)
	}
	resolveResp, err := c.resolve(&emulators.ResolveRequest{Target: "foo"})
	if err != nil {
		t.Fatal(err)
	}
	if resolveResp.Target != "bar" {
		t.Fatalf("Expected bar: %v", resolveResp.Target)
	}
}
<|endoftext|>


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

// Test mutator availability and end-to-end latency of an RPC-like system.
package main

// TODO: This benchmark doesn't target a particular aspect of the GC
// like other gcbench benchmarks do. It would be nice if we just had
// composable sets of metrics that a benchmark could opt in to without
// needing a whole "framework", but I'm not sure how to do that with
// the gctrace on stderr thing. Maybe we could dup our own FD over
// stderr to read our own gctrace? That's dangerous if something goes
// wrong since you'll lose the panic. We could pipe it by way of an
// external program that could report if something goes wrong (e.g.,
// tee if you just want to see the whole log). This might also make it
// easier to ignore initialization and warm-up in the GC log.

import (
	"bufio"
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"math/rand"
	"net"
	"os"
	"os/exec"
	"sync"
	"sync/atomic"
	"time"

	"github.com/aclements/go-gcbench/gcbench"
	"golang.org/x/benchmarks/gcbench.newer/heapgen"
)

var (
	flagDuration   = flag.Duration("benchtime", 10*time.Second, "steady state duration")
	flagBallast    = gcbench.FlagBytes("ballast", 64*gcbench.MB, "retain `x` bytes of global data")
	flagReqsPerSec = flag.Float64("reqs-per-sec", 10000, "send `rate` requests per second")
	flagClient     = flag.String("client", "", "internal flag to act as RPC client")
)

func main() {
	flag.Parse()
	if flag.NArg() > 0 {
		flag.Usage()
		os.Exit(2)
	}

	// TODO: Report end-to-end latency measured by the client as a
	// metric.

	if *flagClient != "" {
		doClient(*flagClient)
		return
	}

	name := "RPC"
	gcbench.NewBenchmark(name, benchMain).Config("reqs-per-sec", *flagReqsPerSec).Config("ballast", *flagBallast).Run()
}

var sink1 interface{}
var requestCount int64

func benchMain() {
	// Create the ballast.
	m := heapgen.Measure(heapgen.MakeAST)
	sink1 = heapgen.Generate(m.Gen, m.BytesRetained, int(*flagBallast))

	// Start the server.
	l := startServer()

	// Start the client.
	client := exec.Command(os.Args[0], "-client", l.Addr().String(), "-reqs-per-sec", fmt.Sprint(*flagReqsPerSec))
	client.Env = []string{}
	cin, err := client.StdinPipe()
	if err != nil {
		log.Fatal("creating client stdin pipe: ", err)
	}
	defer cin.Close()
	cout, err := client.StdoutPipe()
	if err != nil {
		log.Fatal("creating client stdout pipe: ", err)
	}
	defer cout.Close()
	client.Stderr = os.Stderr
	err = client.Start()
	if err != nil {
		log.Fatal("starting client: ", err)
	}

	// Run the server.
	done := make(chan bool)
	startTime := time.Now()
	time.AfterFunc(*flagDuration, func() {
		gcbench.ReportExtra("reqs/sec", float64(atomic.LoadInt64(&requestCount))/float64(time.Since(startTime))*float64(time.Second))

		// Shut down client.
		cin.Close()
		err := client.Wait()
		if err != nil {
			log.Fatal("client failed: ", err)
		}
		close(done)
	})
	go func() {
		for {
			c, err := l.Accept()
			if err != nil {
				log.Fatal("accept: ", err)
			}
			go handler(c)
		}
	}()
	<-done
}

func startServer() net.Listener {
	l, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		if l, err = net.Listen("tcp6", "[::1]:0"); err != nil {
			log.Fatalf("failed to listen: %v", err)
		}
	}
	return l
}

func handler(c net.Conn) {
	var header [1]byte
	r, w := bufio.NewReader(c), bufio.NewWriter(c)
	d := json.NewDecoder(r)
	for {
		// Read the request.
		n, err := r.Read(header[:])
		if err == io.EOF {
			c.Close()
			return
		}
		if n != 1 || header[0] != 'x' {
			log.Fatal("bad header: ", n, err, header)
		}

		var data interface{}
		err = d.Decode(&data)
		if err != nil {
			log.Fatal("decoding request: ", err)
		}

		atomic.AddInt64(&requestCount, 1)

		// Send the response.
		json.NewEncoder(w).Encode(data)
		w.Flush()
	}
}

func doClient(addr string) {
	var wg sync.WaitGroup
	stop := make(chan bool)

	var lat gcbench.LatencyDist

	// Wait for stdin to close, then shut down cleanly.
	go func() {
		var buf [1024]byte
		for {
			n, err := os.Stdin.Read(buf[:])
			if n <= 0 || err != nil {
				break
			}
		}
		log.Print("cleaning up client")
		close(stop)
		wg.Wait()
		log.Print("closing ", len(conns.q), " connections")
		for _, c := range conns.q {
			c.Close()
		}
		fmt.Fprintln(os.Stderr, "client-measured request latency:")
		lat.Fprint(os.Stderr)
		//log.Print("client exiting")
		os.Exit(0)
	}()

	// Generate a Poisson process by spacing events out by an
	// exponential distribution and using open-loop control. To
	// minimize the bottleneck in the coordinator, we shard this
	// process across several goroutines.
	const shards = 16
	reqsPerNSPerShard := *flagReqsPerSec / 1e9 / shards
	for i := 0; i < shards; i++ {
		wg.Add(1)
		go func() {
		loop:
			for {
				wait := rand.ExpFloat64() / reqsPerNSPerShard
				select {
				case <-stop:
					break loop
				case <-time.After(time.Duration(wait)):
				}
				wg.Add(1)
				go sendOneRequest(addr, &wg, &lat)
			}
			wg.Done()
		}()
	}
	select {}
}

var reqBytes = []byte(`x{
	"Dir": "src/runtime",
	"ImportPath": "runtime",
	"Name": "runtime",
	"Doc": "Package runtime contains operations that interact with Go's runtime system, such as functions to control goroutines.",
	"Target": "pkg/linux_amd64/runtime.a",
	"Goroot": true,
	"Standard": true,
	"Stale": true,
	"StaleReason": "stale dependency",
	"Root": "go.dev"
}`)

var conns struct {
	q []net.Conn
	sync.Mutex
}

func sendOneRequest(addr string, wg *sync.WaitGroup, lat *gcbench.LatencyDist) {
	defer wg.Done()

	var c net.Conn
	conns.Lock()
	if len(conns.q) > 0 {
		c = conns.q[len(conns.q)-1]
		conns.q = conns.q[:len(conns.q)-1]
	}
	conns.Unlock()
	if c == nil {
		var err error
		c, err = net.Dial("tcp", addr)
		if err != nil {
			// This could just mean the server is
			// overloaded. (TODO: Maybe it's this single
			// Accept that gets overloaded?)
			log.Print("dialing ", addr, ": ", err)
			return
		}
	}

	// Send the request.
	//log.Print("sending request", c)
	lt := lat.Start()
	n, err := c.Write(reqBytes)
	if err != nil {
		log.Fatal("writing request: ", err)
	}
	if n != len(reqBytes) {
		log.Fatal("short write")
	}
	//log.Print("sent request; reading response", c)

	// Read the response.
	var buf [1024]byte
	var done bool
	for !done {
		n, err := c.Read(buf[:])
		if bytes.IndexRune(buf[:n], '\n') >= 0 {
			done = true
		}
		if err == io.EOF {
			c.Close()
			return
		}
		if err != nil {
			log.Fatal("reading response: ", err)
		}
	}
	lt.Tick()
	//log.Print("read response", c)

	conns.Lock()
	conns.q = append(conns.q, c)
	conns.Unlock()
}
<commit_msg>gcbench/progs: fix bad import of heapgen<commit_after>// Copyright 2016 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.

// Test mutator availability and end-to-end latency of an RPC-like system.
package main

// TODO: This benchmark doesn't target a particular aspect of the GC
// like other gcbench benchmarks do. It would be nice if we just had
// composable sets of metrics that a benchmark could opt in to without
// needing a whole "framework", but I'm not sure how to do that with
// the gctrace on stderr thing. Maybe we could dup our own FD over
// stderr to read our own gctrace? That's dangerous if something goes
// wrong since you'll lose the panic. We could pipe it by way of an
// external program that could report if something goes wrong (e.g.,
// tee if you just want to see the whole log). This might also make it
// easier to ignore initialization and warm-up in the GC log.

import (
	"bufio"
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"math/rand"
	"net"
	"os"
	"os/exec"
	"sync"
	"sync/atomic"
	"time"

	"github.com/aclements/go-gcbench/gcbench"
	"github.com/aclements/go-gcbench/gcbench/heapgen"
)

var (
	flagDuration   = flag.Duration("benchtime", 10*time.Second, "steady state duration")
	flagBallast    = gcbench.FlagBytes("ballast", 64*gcbench.MB, "retain `x` bytes of global data")
	flagReqsPerSec = flag.Float64("reqs-per-sec", 10000, "send `rate` requests per second")
	flagClient     = flag.String("client", "", "internal flag to act as RPC client")
)

func main() {
	flag.Parse()
	if flag.NArg() > 0 {
		flag.Usage()
		os.Exit(2)
	}

	// TODO: Report end-to-end latency measured by the client as a
	// metric.

	if *flagClient != "" {
		doClient(*flagClient)
		return
	}

	name := "RPC"
	gcbench.NewBenchmark(name, benchMain).Config("reqs-per-sec", *flagReqsPerSec).Config("ballast", *flagBallast).Run()
}

var sink1 interface{}
var requestCount int64

func benchMain() {
	// Create the ballast.
	m := heapgen.Measure(heapgen.MakeAST)
	sink1 = heapgen.Generate(m.Gen, m.BytesRetained, int(*flagBallast))

	// Start the server.
	l := startServer()

	// Start the client.
	client := exec.Command(os.Args[0], "-client", l.Addr().String(), "-reqs-per-sec", fmt.Sprint(*flagReqsPerSec))
	client.Env = []string{}
	cin, err := client.StdinPipe()
	if err != nil {
		log.Fatal("creating client stdin pipe: ", err)
	}
	defer cin.Close()
	cout, err := client.StdoutPipe()
	if err != nil {
		log.Fatal("creating client stdout pipe: ", err)
	}
	defer cout.Close()
	client.Stderr = os.Stderr
	err = client.Start()
	if err != nil {
		log.Fatal("starting client: ", err)
	}

	// Run the server.
	done := make(chan bool)
	startTime := time.Now()
	time.AfterFunc(*flagDuration, func() {
		gcbench.ReportExtra("reqs/sec", float64(atomic.LoadInt64(&requestCount))/float64(time.Since(startTime))*float64(time.Second))

		// Shut down client.
		cin.Close()
		err := client.Wait()
		if err != nil {
			log.Fatal("client failed: ", err)
		}
		close(done)
	})
	go func() {
		for {
			c, err := l.Accept()
			if err != nil {
				log.Fatal("accept: ", err)
			}
			go handler(c)
		}
	}()
	<-done
}

func startServer() net.Listener {
	l, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		if l, err = net.Listen("tcp6", "[::1]:0"); err != nil {
			log.Fatalf("failed to listen: %v", err)
		}
	}
	return l
}

func handler(c net.Conn) {
	var header [1]byte
	r, w := bufio.NewReader(c), bufio.NewWriter(c)
	d := json.NewDecoder(r)
	for {
		// Read the request.
		n, err := r.Read(header[:])
		if err == io.EOF {
			c.Close()
			return
		}
		if n != 1 || header[0] != 'x' {
			log.Fatal("bad header: ", n, err, header)
		}

		var data interface{}
		err = d.Decode(&data)
		if err != nil {
			log.Fatal("decoding request: ", err)
		}

		atomic.AddInt64(&requestCount, 1)

		// Send the response.
		json.NewEncoder(w).Encode(data)
		w.Flush()
	}
}

func doClient(addr string) {
	var wg sync.WaitGroup
	stop := make(chan bool)

	var lat gcbench.LatencyDist

	// Wait for stdin to close, then shut down cleanly.
	go func() {
		var buf [1024]byte
		for {
			n, err := os.Stdin.Read(buf[:])
			if n <= 0 || err != nil {
				break
			}
		}
		log.Print("cleaning up client")
		close(stop)
		wg.Wait()
		log.Print("closing ", len(conns.q), " connections")
		for _, c := range conns.q {
			c.Close()
		}
		fmt.Fprintln(os.Stderr, "client-measured request latency:")
		lat.Fprint(os.Stderr)
		//log.Print("client exiting")
		os.Exit(0)
	}()

	// Generate a Poisson process by spacing events out by an
	// exponential distribution and using open-loop control. To
	// minimize the bottleneck in the coordinator, we shard this
	// process across several goroutines.
	const shards = 16
	reqsPerNSPerShard := *flagReqsPerSec / 1e9 / shards
	for i := 0; i < shards; i++ {
		wg.Add(1)
		go func() {
		loop:
			for {
				wait := rand.ExpFloat64() / reqsPerNSPerShard
				select {
				case <-stop:
					break loop
				case <-time.After(time.Duration(wait)):
				}
				wg.Add(1)
				go sendOneRequest(addr, &wg, &lat)
			}
			wg.Done()
		}()
	}
	select {}
}

var reqBytes = []byte(`x{
	"Dir": "src/runtime",
	"ImportPath": "runtime",
	"Name": "runtime",
	"Doc": "Package runtime contains operations that interact with Go's runtime system, such as functions to control goroutines.",
	"Target": "pkg/linux_amd64/runtime.a",
	"Goroot": true,
	"Standard": true,
	"Stale": true,
	"StaleReason": "stale dependency",
	"Root": "go.dev"
}`)

var conns struct {
	q []net.Conn
	sync.Mutex
}

func sendOneRequest(addr string, wg *sync.WaitGroup, lat *gcbench.LatencyDist) {
	defer wg.Done()

	var c net.Conn
	conns.Lock()
	if len(conns.q) > 0 {
		c = conns.q[len(conns.q)-1]
		conns.q = conns.q[:len(conns.q)-1]
	}
	conns.Unlock()
	if c == nil {
		var err error
		c, err = net.Dial("tcp", addr)
		if err != nil {
			// This could just mean the server is
			// overloaded. (TODO: Maybe it's this single
			// Accept that gets overloaded?)
			log.Print("dialing ", addr, ": ", err)
			return
		}
	}

	// Send the request.
	//log.Print("sending request", c)
	lt := lat.Start()
	n, err := c.Write(reqBytes)
	if err != nil {
		log.Fatal("writing request: ", err)
	}
	if n != len(reqBytes) {
		log.Fatal("short write")
	}
	//log.Print("sent request; reading response", c)

	// Read the response.
	var buf [1024]byte
	var done bool
	for !done {
		n, err := c.Read(buf[:])
		if bytes.IndexRune(buf[:n], '\n') >= 0 {
			done = true
		}
		if err == io.EOF {
			c.Close()
			return
		}
		if err != nil {
			log.Fatal("reading response: ", err)
		}
	}
	lt.Tick()
	//log.Print("read response", c)

	conns.Lock()
	conns.q = append(conns.q, c)
	conns.Unlock()
}
<|endoftext|>


<commit_before>// Copyright 2014 Unknwon
//
// 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 routers

import (
	"fmt"

	"github.com/gpmgo/switch/models"
	"github.com/gpmgo/switch/modules/middleware"
)

func Package(ctx *middleware.Context) {
	importPath := ctx.Params("*")
	ctx.Data["Title"] = importPath
	ctx.Data["ImportPath"] = importPath
	ctx.HTML(200, "package")
}

func Badge(ctx *middleware.Context) {
	importPath := ctx.Params("*")
	pkg, err := models.GetPakcageByPath(importPath)
	if err != nil {
		if err == models.ErrPackageNotExist {
			ctx.Error(404)
		} else {
			ctx.Handle(500, "Badge", err)
		}
		return
	}
	ctx.Redirect(fmt.Sprintf("http://img.shields.io/badge/downloads-%d_total-blue.svg?style=flat", pkg.DownloadCount))
}
<commit_msg>Single page for packages<commit_after>// Copyright 2014 Unknwon
//
// 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 routers

import (
	"fmt"

	"github.com/gpmgo/switch/models"
	"github.com/gpmgo/switch/modules/middleware"
)

func Package(ctx *middleware.Context) {
	importPath := ctx.Params("*")
	_, err := models.GetPakcageByPath(importPath)
	if err != nil {
		if err == models.ErrPackageNotExist {
			ctx.Handle(404, "Package", nil)
		} else {
			ctx.Handle(500, "Package", err)
		}
		return
	}

	ctx.Data["Title"] = importPath
	ctx.Data["ImportPath"] = importPath
	ctx.HTML(200, "package")
}

func Badge(ctx *middleware.Context) {
	importPath := ctx.Params("*")
	pkg, err := models.GetPakcageByPath(importPath)
	if err != nil {
		if err == models.ErrPackageNotExist {
			ctx.Error(404)
		} else {
			ctx.Handle(500, "Badge", err)
		}
		return
	}
	ctx.Redirect(fmt.Sprintf("http://img.shields.io/badge/downloads-%d_total-blue.svg?style=flat", pkg.DownloadCount))
}
<|endoftext|>


<commit_before>// Copyright © 2016 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package mqtt

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"strings"

	"github.com/TheThingsNetwork/ttn/core"
	"github.com/TheThingsNetwork/ttn/utils/errors"
	"github.com/TheThingsNetwork/ttn/utils/stats"
	"github.com/apex/log"
	"github.com/yosssi/gmq/mqtt"
	"github.com/yosssi/gmq/mqtt/client"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
)

// Interface defines a public interface for the mqtt adapter
type Interface interface {
	core.AppClient
	Start(inMsg <-chan Msg, handler core.HandlerServer)
}

type adapter struct {
	Components
}

// Components defines a structure to make the instantiation easier to read
type Components struct {
	Client Client
	Ctx    log.Interface
}

// Options defines a structure to make the instantiation easier to read
type Options struct {
}

// Msg are emitted by an MQTT subscriber towards the adapter
type Msg struct {
	Topic   string
	Payload []byte
	Type    msgType
}

// msgType constants are used in MQTTMsg to characterise the kind of message processed
const (
	Down msgType = iota + 1
)

type msgType byte

// New constructs an mqtt adapter responsible for making the bridge between the handler and
// application.
func New(c Components, o Options) Interface {
	return adapter{Components: c}
}

// Start eventually launches the mqtt message internal consumer
func (a adapter) Start(inMsg <-chan Msg, handler core.HandlerServer) {
	go a.consumeMQTTMsg(inMsg, handler)
}

// HandleData implements the core.AppClient interface
func (a adapter) HandleData(bctx context.Context, req *core.DataAppReq, _ ...grpc.CallOption) (*core.DataAppRes, error) {
	stats.MarkMeter("mqtt_adapter.send")

	// Verify the packet integrity
	// TODO Move this elsewhere, make it a function call validate() ...
	if req == nil {
		stats.MarkMeter("mqtt_adapter.uplink.invalid")
		return nil, errors.New(errors.Structural, "Received Nil Application Request")
	}
	if len(req.Payload) == 0 {
		stats.MarkMeter("mqtt_adapter.uplink.invalid")
		return nil, errors.New(errors.Structural, "Invalid Packet Payload")
	}
	if len(req.DevEUI) != 8 {
		stats.MarkMeter("mqtt_adapter.uplink.invalid")
		return nil, errors.New(errors.Structural, "Invalid Device EUI")
	}
	if len(req.AppEUI) != 8 {
		stats.MarkMeter("mqtt_adapter.uplink.invalid")
		return nil, errors.New(errors.Structural, "Invalid Application EUI")
	}
	if req.Metadata == nil {
		stats.MarkMeter("mqtt_adapter.uplink.invalid")
		return nil, errors.New(errors.Structural, "Missing Mandatory Metadata")
	}
	ctx := a.Ctx.WithField("appEUI", req.AppEUI).WithField("devEUI", req.DevEUI)

	// Marshal the packet
	dataUp := core.DataUpAppReq{
		Payload:  req.Payload,
		Metadata: core.ProtoMetaToAppMeta(req.Metadata...),
	}
	msg, err := dataUp.MarshalMsg(nil)
	if err != nil {
		return nil, errors.New(errors.Structural, "Unable to marshal the application payload")
	}

	// Actually send it
	ctx.Debug("Sending Packet")
	deui, aeui := hex.EncodeToString(req.DevEUI), hex.EncodeToString(req.AppEUI)
	err = a.Client.Publish(&client.PublishOptions{
		QoS:       mqtt.QoS2,
		Retain:    false,
		TopicName: []byte(fmt.Sprintf("%s/devices/%s/up", aeui, deui)),
		Message:   msg,
	})

	if err != nil {
		return nil, errors.New(errors.Operational, err)
	}
	return nil, nil
}

// HandleJoin implements the core.AppClient interface
func (a adapter) HandleJoin(bctx context.Context, req *core.JoinAppReq, _ ...grpc.CallOption) (*core.JoinAppRes, error) {
	if len(req.AppEUI) != 8 || len(req.DevEUI) != 8 || len(req.Metadata) == 0 {
		a.Ctx.Debug("Received invalid JoinAppReq")
		return nil, errors.New(errors.Structural, "Invalid request parameters")
	}
	otaa := core.OTAAAppReq{
		Metadata: core.ProtoMetaToAppMeta(req.Metadata...),
	}
	data, err := otaa.MarshalMsg(nil)
	if err != nil {
		return nil, errors.New(errors.Structural, err)
	}
	deui, aeui := hex.EncodeToString(req.DevEUI), hex.EncodeToString(req.AppEUI)
	err = a.Client.Publish(&client.PublishOptions{
		QoS:       mqtt.QoS2,
		Retain:    false,
		TopicName: []byte(fmt.Sprintf("%s/devices/%s/up", aeui, deui)),
		Message:   data,
	})
	if err != nil {
		return nil, errors.New(errors.Operational, err)
	}
	return nil, nil
}

// consumeMQTTMsg processes incoming messages from MQTT broker.
//
// It runs in its own goroutine
func (a adapter) consumeMQTTMsg(chmsg <-chan Msg, handler core.HandlerServer) {
	a.Ctx.Debug("Start consuming MQTT messages")
	for msg := range chmsg {
		switch msg.Type {
		case Down:
			req, err := handleDataDown(msg)
			if err == nil {
				_, err = handler.HandleDataDown(context.Background(), req)
			}
			if err != nil {
				a.Ctx.WithError(err).Debug("Unable to consume data down")
			}
		default:
			a.Ctx.Debug("Unsupported MQTT message's type")
		}
	}
	a.Ctx.Debug("Stop consuming MQTT messages")
}

// handleDataDown parses and handles Downlink message coming through MQTT
func handleDataDown(msg Msg) (*core.DataDownHandlerReq, error) {
	// Ensure the query / topic parameters are valid
	topicInfos := strings.Split(msg.Topic, "/")
	if len(topicInfos) != 4 {
		return nil, errors.New(errors.Structural, "Unexpect (and invalid) mqtt topic")
	}
	appEUI, erra := hex.DecodeString(topicInfos[0])
	devEUI, errd := hex.DecodeString(topicInfos[2])
	if erra != nil || errd != nil || len(appEUI) != 8 || len(devEUI) != 8 {
		return nil, errors.New(errors.Structural, "Topic constituted of invalid AppEUI or DevEUI")
	}

	// Retrieve the message payload
	var req core.DataDownAppReq
	if _, err := req.UnmarshalMsg(msg.Payload); err != nil {
		if err = json.Unmarshal(msg.Payload, &req); err != nil {
			return nil, errors.New(errors.Structural, err)
		}
	}
	if len(req.Payload) == 0 {
		return nil, errors.New(errors.Structural, "There's now data to handle")
	}

	// Convert it to an handler downlink
	return &core.DataDownHandlerReq{
		Payload: req.Payload,
		TTL:     req.TTL,
		AppEUI:  appEUI,
		DevEUI:  devEUI,
	}, nil
}
<commit_msg>[debt/metadata] Fix wrong topic being used by mqtt adapter<commit_after>// Copyright © 2016 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package mqtt

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"strings"

	"github.com/TheThingsNetwork/ttn/core"
	"github.com/TheThingsNetwork/ttn/utils/errors"
	"github.com/TheThingsNetwork/ttn/utils/stats"
	"github.com/apex/log"
	"github.com/yosssi/gmq/mqtt"
	"github.com/yosssi/gmq/mqtt/client"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
)

// Interface defines a public interface for the mqtt adapter
type Interface interface {
	core.AppClient
	Start(inMsg <-chan Msg, handler core.HandlerServer)
}

type adapter struct {
	Components
}

// Components defines a structure to make the instantiation easier to read
type Components struct {
	Client Client
	Ctx    log.Interface
}

// Options defines a structure to make the instantiation easier to read
type Options struct {
}

// Msg are emitted by an MQTT subscriber towards the adapter
type Msg struct {
	Topic   string
	Payload []byte
	Type    msgType
}

// msgType constants are used in MQTTMsg to characterise the kind of message processed
const (
	Down msgType = iota + 1
)

type msgType byte

// New constructs an mqtt adapter responsible for making the bridge between the handler and
// application.
func New(c Components, o Options) Interface {
	return adapter{Components: c}
}

// Start eventually launches the mqtt message internal consumer
func (a adapter) Start(inMsg <-chan Msg, handler core.HandlerServer) {
	go a.consumeMQTTMsg(inMsg, handler)
}

// HandleData implements the core.AppClient interface
func (a adapter) HandleData(bctx context.Context, req *core.DataAppReq, _ ...grpc.CallOption) (*core.DataAppRes, error) {
	stats.MarkMeter("mqtt_adapter.send")

	// Verify the packet integrity
	// TODO Move this elsewhere, make it a function call validate() ...
	if req == nil {
		stats.MarkMeter("mqtt_adapter.uplink.invalid")
		return nil, errors.New(errors.Structural, "Received Nil Application Request")
	}
	if len(req.Payload) == 0 {
		stats.MarkMeter("mqtt_adapter.uplink.invalid")
		return nil, errors.New(errors.Structural, "Invalid Packet Payload")
	}
	if len(req.DevEUI) != 8 {
		stats.MarkMeter("mqtt_adapter.uplink.invalid")
		return nil, errors.New(errors.Structural, "Invalid Device EUI")
	}
	if len(req.AppEUI) != 8 {
		stats.MarkMeter("mqtt_adapter.uplink.invalid")
		return nil, errors.New(errors.Structural, "Invalid Application EUI")
	}
	if req.Metadata == nil {
		stats.MarkMeter("mqtt_adapter.uplink.invalid")
		return nil, errors.New(errors.Structural, "Missing Mandatory Metadata")
	}
	ctx := a.Ctx.WithField("appEUI", req.AppEUI).WithField("devEUI", req.DevEUI)

	// Marshal the packet
	dataUp := core.DataUpAppReq{
		Payload:  req.Payload,
		Metadata: core.ProtoMetaToAppMeta(req.Metadata...),
	}
	msg, err := dataUp.MarshalMsg(nil)
	if err != nil {
		return nil, errors.New(errors.Structural, "Unable to marshal the application payload")
	}

	// Actually send it
	ctx.Debug("Sending Packet")
	deui, aeui := hex.EncodeToString(req.DevEUI), hex.EncodeToString(req.AppEUI)
	err = a.Client.Publish(&client.PublishOptions{
		QoS:       mqtt.QoS2,
		Retain:    false,
		TopicName: []byte(fmt.Sprintf("%s/devices/%s/up", aeui, deui)),
		Message:   msg,
	})

	if err != nil {
		return nil, errors.New(errors.Operational, err)
	}
	return nil, nil
}

// HandleJoin implements the core.AppClient interface
func (a adapter) HandleJoin(bctx context.Context, req *core.JoinAppReq, _ ...grpc.CallOption) (*core.JoinAppRes, error) {
	if len(req.AppEUI) != 8 || len(req.DevEUI) != 8 || len(req.Metadata) == 0 {
		a.Ctx.Debug("Received invalid JoinAppReq")
		return nil, errors.New(errors.Structural, "Invalid request parameters")
	}
	otaa := core.OTAAAppReq{
		Metadata: core.ProtoMetaToAppMeta(req.Metadata...),
	}
	data, err := otaa.MarshalMsg(nil)
	if err != nil {
		return nil, errors.New(errors.Structural, err)
	}
	deui, aeui := hex.EncodeToString(req.DevEUI), hex.EncodeToString(req.AppEUI)
	err = a.Client.Publish(&client.PublishOptions{
		QoS:       mqtt.QoS2,
		Retain:    false,
		TopicName: []byte(fmt.Sprintf("%s/devices/%s/activations", aeui, deui)),
		Message:   data,
	})
	if err != nil {
		return nil, errors.New(errors.Operational, err)
	}
	return nil, nil
}

// consumeMQTTMsg processes incoming messages from MQTT broker.
//
// It runs in its own goroutine
func (a adapter) consumeMQTTMsg(chmsg <-chan Msg, handler core.HandlerServer) {
	a.Ctx.Debug("Start consuming MQTT messages")
	for msg := range chmsg {
		switch msg.Type {
		case Down:
			req, err := handleDataDown(msg)
			if err == nil {
				_, err = handler.HandleDataDown(context.Background(), req)
			}
			if err != nil {
				a.Ctx.WithError(err).Debug("Unable to consume data down")
			}
		default:
			a.Ctx.Debug("Unsupported MQTT message's type")
		}
	}
	a.Ctx.Debug("Stop consuming MQTT messages")
}

// handleDataDown parses and handles Downlink message coming through MQTT
func handleDataDown(msg Msg) (*core.DataDownHandlerReq, error) {
	// Ensure the query / topic parameters are valid
	topicInfos := strings.Split(msg.Topic, "/")
	if len(topicInfos) != 4 {
		return nil, errors.New(errors.Structural, "Unexpect (and invalid) mqtt topic")
	}
	appEUI, erra := hex.DecodeString(topicInfos[0])
	devEUI, errd := hex.DecodeString(topicInfos[2])
	if erra != nil || errd != nil || len(appEUI) != 8 || len(devEUI) != 8 {
		return nil, errors.New(errors.Structural, "Topic constituted of invalid AppEUI or DevEUI")
	}

	// Retrieve the message payload
	var req core.DataDownAppReq
	if _, err := req.UnmarshalMsg(msg.Payload); err != nil {
		if err = json.Unmarshal(msg.Payload, &req); err != nil {
			return nil, errors.New(errors.Structural, err)
		}
	}
	if len(req.Payload) == 0 {
		return nil, errors.New(errors.Structural, "There's now data to handle")
	}

	// Convert it to an handler downlink
	return &core.DataDownHandlerReq{
		Payload: req.Payload,
		TTL:     req.TTL,
		AppEUI:  appEUI,
		DevEUI:  devEUI,
	}, nil
}
<|endoftext|>


<commit_before>package core_test

import (
	"fmt"
	"github.com/APTrust/bagit/core"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"path/filepath"
	"runtime"
	"testing"
)

func getPathToProfile(profileName string) (string, error) {
	_, filename, _, ok := runtime.Caller(0)
	if !ok {
		return "", fmt.Errorf("Rumtime cannot get caller file name.")
	}
	absFileName, err := filepath.Abs(filename)
	if err != nil {
		return "", err
	}
	bagPath := filepath.Join(absFileName, "..", "..", "testdata", "profiles", profileName)
	return filepath.Abs(bagPath)
}

func TestLoadBagItProfile(t *testing.T) {
	aptrustFile, err := getPathToProfile("aptrust_bagit_profile_2.0.json")
	require.Nil(t, err)
	profile, err := core.LoadBagItProfile(aptrustFile)
	require.Nil(t, err)
	assert.EqualValues(t, []string{"0.97"}, profile.AcceptBagItVersion)
	assert.EqualValues(t, []string{"application/tar"}, profile.AcceptSerialization)
	assert.False(t, profile.AllowFetchTxt)
	assert.True(t, profile.AllowMiscTopLevelFiles)
	assert.True(t, profile.AllowMiscDirectories)
	assert.EqualValues(t, []string{"md5"}, profile.ManifestsRequired)
	assert.Equal(t, "required", profile.Serialization)
	assert.Empty(t, profile.TagManifestsRequired)

	// BagIt Profile Info
	require.NotNil(t, profile.BagItProfileInfo)
	assert.Equal(t, "http://example.com/aptrust-bagit-profile_2.0.json", profile.BagItProfileInfo.BagItProfileIdentifier)
	assert.Equal(t, "support@aptrust.org", profile.BagItProfileInfo.ContactEmail)
	assert.Equal(t, "A. Diamond", profile.BagItProfileInfo.ContactName)
	assert.Equal(t, "BagIt profile for ingesting content into APTrust.", profile.BagItProfileInfo.ExternalDescription)
	assert.Equal(t, "aptrust.org", profile.BagItProfileInfo.SourceOrganization)
	assert.Equal(t, "2.0", profile.BagItProfileInfo.Version)

	// Required Tag Files
	require.NotNil(t, profile.TagFilesRequired)
	require.Equal(t, 3, len(profile.TagFilesRequired))

	bagit := profile.TagFilesRequired["bagit.txt"]
	require.NotNil(t, bagit)
	require.Equal(t, 2, len(bagit))
	require.NotNil(t, bagit["BagIt-Version"])
	assert.True(t, bagit["BagIt-Version"].Required)
	assert.False(t, bagit["BagIt-Version"].EmptyOk)

	baginfo := profile.TagFilesRequired["bag-info.txt"]
	require.NotNil(t, baginfo)

	aptrust := profile.TagFilesRequired["aptrust-info.txt"]
	require.NotNil(t, aptrust)

	// Make sure this one parses, while we're at it.
	dpnFile, err := getPathToProfile("dpn_bagit_profile.json")
	require.Nil(t, err)
	dpnProfile, err := core.LoadBagItProfile(dpnFile)
	require.Nil(t, err)
	require.Equal(t, 3, len(dpnProfile.TagFilesRequired))
	require.Equal(t, 2, len(dpnProfile.TagFilesRequired["bagit.txt"]))
	require.Equal(t, 9, len(dpnProfile.TagFilesRequired["bag-info.txt"]))
	require.Equal(t, 11, len(dpnProfile.TagFilesRequired["dpn-tags/dpn-info.txt"]))
}

func TestBagItProfileValidate(t *testing.T) {
	profile := &core.BagItProfile{}
	errs := profile.Validate()
	require.Equal(t, 4, len(errs))

	profile.AcceptBagItVersion = []string{"0.97"}
	profile.ManifestsRequired = []string{"md5"}
	profile.TagFilesRequired = make(map[string]map[string]*core.TagDefinition)
	profile.TagFilesRequired["bagit.txt"] = make(map[string]*core.TagDefinition)
	profile.TagFilesRequired["bagit.txt"]["tag1"] = &core.TagDefinition{}
	profile.TagFilesRequired["bag-info.txt"] = make(map[string]*core.TagDefinition)
	profile.TagFilesRequired["bag-info.txt"]["tag2"] = &core.TagDefinition{}
	errs = profile.Validate()
	require.Equal(t, 0, len(errs))
}

func TestRequiredTagDirs(t *testing.T) {
	aptrustFile, err := getPathToProfile("aptrust_bagit_profile_2.0.json")
	require.Nil(t, err)
	aptrustProfile, err := core.LoadBagItProfile(aptrustFile)
	require.Nil(t, err)
	assert.Empty(t, aptrustProfile.RequiredTagDirs())

	dpnFile, err := getPathToProfile("dpn_bagit_profile.json")
	require.Nil(t, err)
	dpnProfile, err := core.LoadBagItProfile(dpnFile)
	require.Nil(t, err)
	require.Equal(t, 1, len(dpnProfile.RequiredTagDirs()))
	assert.Equal(t, "dpn-tags", dpnProfile.RequiredTagDirs()[0])
}
<commit_msg>Test that tag labels are loaded properly<commit_after>package core_test

import (
	"fmt"
	"github.com/APTrust/bagit/core"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"path/filepath"
	"runtime"
	"testing"
)

func getPathToProfile(profileName string) (string, error) {
	_, filename, _, ok := runtime.Caller(0)
	if !ok {
		return "", fmt.Errorf("Rumtime cannot get caller file name.")
	}
	absFileName, err := filepath.Abs(filename)
	if err != nil {
		return "", err
	}
	bagPath := filepath.Join(absFileName, "..", "..", "testdata", "profiles", profileName)
	return filepath.Abs(bagPath)
}

func TestLoadBagItProfile(t *testing.T) {
	aptrustFile, err := getPathToProfile("aptrust_bagit_profile_2.0.json")
	require.Nil(t, err)
	profile, err := core.LoadBagItProfile(aptrustFile)
	require.Nil(t, err)
	assert.EqualValues(t, []string{"0.97"}, profile.AcceptBagItVersion)
	assert.EqualValues(t, []string{"application/tar"}, profile.AcceptSerialization)
	assert.False(t, profile.AllowFetchTxt)
	assert.True(t, profile.AllowMiscTopLevelFiles)
	assert.True(t, profile.AllowMiscDirectories)
	assert.EqualValues(t, []string{"md5"}, profile.ManifestsRequired)
	assert.Equal(t, "required", profile.Serialization)
	assert.Empty(t, profile.TagManifestsRequired)

	// BagIt Profile Info
	require.NotNil(t, profile.BagItProfileInfo)
	assert.Equal(t, "http://example.com/aptrust-bagit-profile_2.0.json", profile.BagItProfileInfo.BagItProfileIdentifier)
	assert.Equal(t, "support@aptrust.org", profile.BagItProfileInfo.ContactEmail)
	assert.Equal(t, "A. Diamond", profile.BagItProfileInfo.ContactName)
	assert.Equal(t, "BagIt profile for ingesting content into APTrust.", profile.BagItProfileInfo.ExternalDescription)
	assert.Equal(t, "aptrust.org", profile.BagItProfileInfo.SourceOrganization)
	assert.Equal(t, "2.0", profile.BagItProfileInfo.Version)

	// Required Tag Files
	require.NotNil(t, profile.TagFilesRequired)
	require.Equal(t, 3, len(profile.TagFilesRequired))

	bagit := profile.TagFilesRequired["bagit.txt"]
	require.NotNil(t, bagit)
	require.Equal(t, 2, len(bagit))
	require.NotNil(t, bagit["BagIt-Version"])
	assert.True(t, bagit["BagIt-Version"].Required)
	assert.False(t, bagit["BagIt-Version"].EmptyOk)

	baginfo := profile.TagFilesRequired["bag-info.txt"]
	require.NotNil(t, baginfo)

	aptrust := profile.TagFilesRequired["aptrust-info.txt"]
	require.NotNil(t, aptrust)

	// Make sure tag labels were copied into tag definitions.
	for _, tagDef := range bagit {
		assert.NotEmpty(t, tagDef.Label)
	}
	for _, tagDef := range baginfo {
		assert.NotEmpty(t, tagDef.Label)
	}
	for _, tagDef := range aptrust {
		assert.NotEmpty(t, tagDef.Label)
	}

	// Make sure this one parses, while we're at it.
	dpnFile, err := getPathToProfile("dpn_bagit_profile.json")
	require.Nil(t, err)
	dpnProfile, err := core.LoadBagItProfile(dpnFile)
	require.Nil(t, err)
	require.Equal(t, 3, len(dpnProfile.TagFilesRequired))
	require.Equal(t, 2, len(dpnProfile.TagFilesRequired["bagit.txt"]))
	require.Equal(t, 9, len(dpnProfile.TagFilesRequired["bag-info.txt"]))
	require.Equal(t, 11, len(dpnProfile.TagFilesRequired["dpn-tags/dpn-info.txt"]))
}

func TestBagItProfileValidate(t *testing.T) {
	profile := &core.BagItProfile{}
	errs := profile.Validate()
	require.Equal(t, 4, len(errs))

	profile.AcceptBagItVersion = []string{"0.97"}
	profile.ManifestsRequired = []string{"md5"}
	profile.TagFilesRequired = make(map[string]map[string]*core.TagDefinition)
	profile.TagFilesRequired["bagit.txt"] = make(map[string]*core.TagDefinition)
	profile.TagFilesRequired["bagit.txt"]["tag1"] = &core.TagDefinition{}
	profile.TagFilesRequired["bag-info.txt"] = make(map[string]*core.TagDefinition)
	profile.TagFilesRequired["bag-info.txt"]["tag2"] = &core.TagDefinition{}
	errs = profile.Validate()
	require.Equal(t, 0, len(errs))
}

func TestRequiredTagDirs(t *testing.T) {
	aptrustFile, err := getPathToProfile("aptrust_bagit_profile_2.0.json")
	require.Nil(t, err)
	aptrustProfile, err := core.LoadBagItProfile(aptrustFile)
	require.Nil(t, err)
	assert.Empty(t, aptrustProfile.RequiredTagDirs())

	dpnFile, err := getPathToProfile("dpn_bagit_profile.json")
	require.Nil(t, err)
	dpnProfile, err := core.LoadBagItProfile(dpnFile)
	require.Nil(t, err)
	require.Equal(t, 1, len(dpnProfile.RequiredTagDirs()))
	assert.Equal(t, "dpn-tags", dpnProfile.RequiredTagDirs()[0])
}
<|endoftext|>


<commit_before>// Copyright 2014 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 rpc

import (
	"errors"
	"fmt"
	"net"
	"strings"
	"testing"
)

type shutdownCodec struct {
	responded chan int
	closed    bool
}

func (c *shutdownCodec) WriteRequest(*Request, interface{}) error { return nil }
func (c *shutdownCodec) ReadResponseBody(interface{}) error       { return nil }
func (c *shutdownCodec) ReadResponseHeader(*Response) error {
	c.responded <- 1
	return errors.New("shutdownCodec ReadResponseHeader")
}
func (c *shutdownCodec) Close() error {
	c.closed = true
	return nil
}

func TestCloseCodec(t *testing.T) {
	codec := &shutdownCodec{responded: make(chan int)}
	client := NewClientWithCodec(codec)
	<-codec.responded
	client.Close()
	if !codec.closed {
		t.Error("client.Close did not close codec")
	}
}

// Test that errors in gob shut down the connection. Issue 7689.

type R struct {
	msg []byte // Not exported, so R does not work with gob.
}

type S struct{}

func (s *S) Recv(nul *struct{}, reply *R) error {
	*reply = R{[]byte("foo")}
	return nil
}

func TestGobError(t *testing.T) {
	if runtime.GOOS == "plan9" {
		t.Skip("skipping test; see http://golang.org/issue/8908")
	}
	defer func() {
		err := recover()
		if err == nil {
			t.Fatal("no error")
		}
		if !strings.Contains("reading body EOF", err.(error).Error()) {
			t.Fatal("expected `reading body EOF', got", err)
		}
	}()
	Register(new(S))

	listen, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		panic(err)
	}
	go Accept(listen)

	client, err := Dial("tcp", listen.Addr().String())
	if err != nil {
		panic(err)
	}

	var reply Reply
	err = client.Call("S.Recv", &struct{}{}, &reply)
	if err != nil {
		panic(err)
	}

	fmt.Printf("%#v\n", reply)
	client.Close()

	listen.Close()
}
<commit_msg>net/rpc: fix build<commit_after>// Copyright 2014 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 rpc

import (
	"errors"
	"fmt"
	"net"
	"runtime"
	"strings"
	"testing"
)

type shutdownCodec struct {
	responded chan int
	closed    bool
}

func (c *shutdownCodec) WriteRequest(*Request, interface{}) error { return nil }
func (c *shutdownCodec) ReadResponseBody(interface{}) error       { return nil }
func (c *shutdownCodec) ReadResponseHeader(*Response) error {
	c.responded <- 1
	return errors.New("shutdownCodec ReadResponseHeader")
}
func (c *shutdownCodec) Close() error {
	c.closed = true
	return nil
}

func TestCloseCodec(t *testing.T) {
	codec := &shutdownCodec{responded: make(chan int)}
	client := NewClientWithCodec(codec)
	<-codec.responded
	client.Close()
	if !codec.closed {
		t.Error("client.Close did not close codec")
	}
}

// Test that errors in gob shut down the connection. Issue 7689.

type R struct {
	msg []byte // Not exported, so R does not work with gob.
}

type S struct{}

func (s *S) Recv(nul *struct{}, reply *R) error {
	*reply = R{[]byte("foo")}
	return nil
}

func TestGobError(t *testing.T) {
	if runtime.GOOS == "plan9" {
		t.Skip("skipping test; see http://golang.org/issue/8908")
	}
	defer func() {
		err := recover()
		if err == nil {
			t.Fatal("no error")
		}
		if !strings.Contains("reading body EOF", err.(error).Error()) {
			t.Fatal("expected `reading body EOF', got", err)
		}
	}()
	Register(new(S))

	listen, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		panic(err)
	}
	go Accept(listen)

	client, err := Dial("tcp", listen.Addr().String())
	if err != nil {
		panic(err)
	}

	var reply Reply
	err = client.Call("S.Recv", &struct{}{}, &reply)
	if err != nil {
		panic(err)
	}

	fmt.Printf("%#v\n", reply)
	client.Close()

	listen.Close()
}
<|endoftext|>


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

package nvim

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"strings"

	"github.com/juju/errors"
	"github.com/neovim-go/vim"
)

const pkgBuffer = "nvim.buffer"

// Buf represents a Neovim buffer.
// TODO(zchee): Ugly type name because of a conflict in vim.Buffer.
type Buf struct {
	v *vim.Vim
	p *vim.Pipeline

	vim.Buffer
	Name     string
	Filetype string
	Bufnr    int
	Mode     string
	Data     []byte

	WindowContext
	TabpageContext
}

// NewBuffer return the new Buf instance with goroutine pipeline.
func NewBuffer(v *vim.Vim) *Buf {
	return &Buf{
		v: v,
		p: v.NewPipeline(),
	}
}

// Create creates the new buffer and return the Buffer structure type.
func (b *Buf) Create(name, filetype, mode string, option map[NvimOption]map[string]interface{}) error {
	b.Name = name
	b.Filetype = filetype
	b.Mode = mode

	err := b.v.Command(fmt.Sprintf("silent %s %s", b.Mode, b.Name))
	if err != nil {
		return errors.Annotate(err, pkgBuffer)
	}

	if err := b.GetBufferContext(); err != nil {
		return errors.Annotate(err, pkgBuffer)
	}

	b.p.BufferNumber(b.Buffer, &b.Bufnr)

	if option != nil {
		if option[BufferOption] != nil {
			for k, op := range option[BufferOption] {
				b.p.SetBufferOption(b.Buffer, k, op)
			}
		}
		if option[BufferVar] != nil {
			for k, op := range option[BufferVar] {
				b.p.SetBufferVar(b.Buffer, k, op, nil)
			}
		}
		if option[WindowOption] != nil {
			for k, op := range option[WindowOption] {
				b.p.SetWindowOption(b.Window, k, op)
			}
		}
		if option[WindowVar] != nil {
			for k, op := range option[WindowVar] {
				b.p.SetWindowVar(b.Window, k, op, nil)
			}
		}
		if option[TabpageVar] != nil {
			for k, op := range option[TabpageVar] {
				b.p.SetTabpageVar(b.Tabpage, k, op, nil)
			}
		}
	}

	if !strings.Contains(b.Name, ".") {
		b.p.Command(fmt.Sprintf("runtime! syntax/%s.vim", filetype))
	}

	return b.p.Wait()
}

func (b *Buf) GetBufferContext() error {
	b.p.CurrentBuffer(&b.Buffer)
	b.p.CurrentWindow(&b.Window)
	b.p.CurrentTabpage(&b.Tabpage)

	return b.p.Wait()
}

func (b *Buf) BufferLines(start, end int, strict bool) {
	if b.Buffer == 0 {
		b.GetBufferContext()
	}

	buf, err := b.v.BufferLines(b.Buffer, start, end, strict)
	if err != nil {
		return
	}
	b.Data = ToByteSlice(buf)
}

func (b *Buf) SetBufferLines(start, end int, strict bool, replacement []byte) error {
	if b.Buffer == 0 {
		err := errors.New("Does not exist of target buffer")
		return err
	}

	b.Data = replacement

	return b.v.SetBufferLines(b.Buffer, start, end, strict, ToBufferLines(replacement))
}

func (b *Buf) SetBufferLinesAll(replacement []byte) error {
	if b.Buffer == 0 {
		err := errors.New("Does not exist of target buffer")
		return err
	}

	b.Data = replacement
	b.Write(b.Data)

	return nil
}

// UpdateSyntax updates the syntax highlight of the buffer.
func (b *Buf) UpdateSyntax(syntax string) {
	if b.Name != "" {
		b.v.SetBufferName(b.Buffer, b.Name)
	}

	if syntax == "" {
		var filetype interface{}
		b.v.BufferOption(b.Buffer, "filetype", &filetype)
		syntax = fmt.Sprintf("%s", filetype)
	}

	b.v.Command(fmt.Sprintf("runtime! syntax/%s.vim", syntax))
}

// SetLocalMapping sets buffer local mapping.
// 'mapping' arg: [key]{destination}
func (b *Buf) SetLocalMapping(mode string, mapping map[string]string) error {
	if mapping != nil {
		cwin, err := b.v.CurrentWindow()
		if err != nil {
			return errors.Annotate(err, "nvim/buffer.SetMapping")
		}

		b.p.SetCurrentWindow(b.Window)
		defer b.v.SetCurrentWindow(cwin)

		for k, v := range mapping {
			b.p.Command(fmt.Sprintf("silent %s <buffer><silent>%s %s", mode, k, v))
		}
	}

	return b.p.Wait()
}

// lineCount counts the Neovim buffer line count and check whether 1 count,
// Because new(empty) buffer and one line buffer are both 1 count.
func (b *Buf) lineCount() (int, error) {
	lineCount, err := b.v.BufferLineCount(b.Buffer)
	if err != nil {
		return 0, errors.Annotate(err, pkgBuffer)
	}

	if lineCount == 1 {
		line, err := b.v.CurrentLine()
		if err != nil {
			return 0, errors.Annotate(err, pkgBuffer)
		}
		// Set 0 to lineCount if buffer is empty
		if len(line) == 0 {
			lineCount = 0
		}
	}

	return lineCount, nil
}

// Write appends the contents of p to the Neovim buffer.
func (b *Buf) Write(p []byte) (int, error) {
	lineCount, err := b.lineCount()
	if err != nil {
		return 0, errors.Annotate(err, pkgBuffer)
	}

	buf := bytes.NewBuffer(p)
	b.v.SetBufferLines(b.Buffer, lineCount, -1, true, ToBufferLines(buf.Bytes()))

	return len(p), nil
}

// WriteString appends the contents of s to the Neovim buffer.
func (b *Buf) WriteString(s string) error {
	lineCount, err := b.lineCount()
	if err != nil {
		return errors.Annotate(err, pkgBuffer)
	}

	buf := bytes.NewBufferString(s)

	return b.v.SetBufferLines(b.Buffer, lineCount, -1, true, ToBufferLines(buf.Bytes()))
}

// Truncate discards all but the first n unread bytes from the
// Neovim buffer but continues to use the same allocated storage.
func (b *Buf) Truncate(n int) {
	b.v.SetBufferLines(b.Buffer, n, -1, true, [][]byte{})
}

// Reset resets the Neovim buffer to be empty,
// but it retains the underlying storage for use by future writes.
// Reset is the same as Truncate(0).
func (b *Buf) Reset() { b.Truncate(0) }

// ----------------------------------------------------------------------------
// Utility

// IsBufferValid wrapper of v.IsBufferValid function.
func IsBufferValid(v *vim.Vim, b vim.Buffer) bool {
	res, err := v.IsBufferValid(b)
	if err != nil {
		return false
	}
	return res
}

// BufContains reports whether buffer list is within b.
func IsBufContains(v *vim.Vim, b vim.Buffer) bool {
	bufs, _ := v.Buffers()
	for _, buf := range bufs {
		if buf == b {
			return true
		}
	}
	return false
}

// BufExists reports whether buffer list is within bufnr use vim bufexists function.
func IsBufExists(v *vim.Vim, bufnr int) bool {
	var res interface{}
	v.Call("bufexists", &res, bufnr)

	return res.(int64) != 0
}

// Modifiable sets modifiable to true,
// The returned function restores modifiable to false.
func Modifiable(v *vim.Vim, b vim.Buffer) func() {
	v.SetBufferOption(b, BufOptionModifiable, true)

	return func() {
		v.SetBufferOption(b, BufOptionModifiable, false)
	}
}

// ToByteSlice converts the 2D buffer byte data to sigle byte slice.
func ToByteSlice(byt [][]byte) []byte { return bytes.Join(byt, []byte{'\n'}) }

// ToBufferLines converts the byte slice to the 2D byte slice of Neovim buffer data.
func ToBufferLines(byt []byte) [][]byte { return bytes.Split(byt, []byte{'\n'}) }

// ByteOffset calculates the byte-offset of current cursor position.
func ByteOffset(v *vim.Vim, b vim.Buffer, w vim.Window) (int, error) {
	cursor, _ := v.WindowCursor(w)
	byteBuf, _ := v.BufferLines(b, 0, -1, true)

	if cursor[0] == 1 {
		return (1 + (cursor[1] - 1)), nil
	}

	var offset int
	line := 1
	for _, buf := range byteBuf {
		if line == cursor[0] {
			offset++
			break
		}
		offset += (binary.Size(buf) + 1)
		line++
	}

	return (offset + (cursor[1] - 1)), nil
}

// ByteOffsetPipe calculates the byte-offset of current cursor position uses vim.Pipeline.
func ByteOffsetPipe(p *vim.Pipeline, b vim.Buffer, w vim.Window) (int, error) {
	var cursor [2]int
	p.WindowCursor(w, &cursor)

	var byteBuf [][]byte
	p.BufferLines(b, 0, -1, true, &byteBuf)

	if err := p.Wait(); err != nil {
		return 0, errors.Annotate(err, "nvim/buffer.ByteOffsetPipe")
	}

	if cursor[0] == 1 {
		return (1 + (cursor[1] - 1)), nil
	}

	offset := 0
	line := 1
	for _, buf := range byteBuf {
		if line == cursor[0] {
			offset++
			break
		}
		offset += (binary.Size(buf) + 1)
		line++
	}

	return (offset + (cursor[1] - 1)), nil
}
<commit_msg>nvim/buffer: Rename IsBufContains to IsBufferContains<commit_after>// Copyright 2016 Koichi Shiraishi. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package nvim

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"strings"

	"github.com/juju/errors"
	"github.com/neovim-go/vim"
)

const pkgBuffer = "nvim.buffer"

// Buf represents a Neovim buffer.
// TODO(zchee): Ugly type name because of a conflict in vim.Buffer.
type Buf struct {
	v *vim.Vim
	p *vim.Pipeline

	vim.Buffer
	Name     string
	Filetype string
	Bufnr    int
	Mode     string
	Data     []byte

	WindowContext
	TabpageContext
}

// NewBuffer return the new Buf instance with goroutine pipeline.
func NewBuffer(v *vim.Vim) *Buf {
	return &Buf{
		v: v,
		p: v.NewPipeline(),
	}
}

// Create creates the new buffer and return the Buffer structure type.
func (b *Buf) Create(name, filetype, mode string, option map[NvimOption]map[string]interface{}) error {
	b.Name = name
	b.Filetype = filetype
	b.Mode = mode

	err := b.v.Command(fmt.Sprintf("silent %s %s", b.Mode, b.Name))
	if err != nil {
		return errors.Annotate(err, pkgBuffer)
	}

	if err := b.GetBufferContext(); err != nil {
		return errors.Annotate(err, pkgBuffer)
	}

	b.p.BufferNumber(b.Buffer, &b.Bufnr)

	if option != nil {
		if option[BufferOption] != nil {
			for k, op := range option[BufferOption] {
				b.p.SetBufferOption(b.Buffer, k, op)
			}
		}
		if option[BufferVar] != nil {
			for k, op := range option[BufferVar] {
				b.p.SetBufferVar(b.Buffer, k, op, nil)
			}
		}
		if option[WindowOption] != nil {
			for k, op := range option[WindowOption] {
				b.p.SetWindowOption(b.Window, k, op)
			}
		}
		if option[WindowVar] != nil {
			for k, op := range option[WindowVar] {
				b.p.SetWindowVar(b.Window, k, op, nil)
			}
		}
		if option[TabpageVar] != nil {
			for k, op := range option[TabpageVar] {
				b.p.SetTabpageVar(b.Tabpage, k, op, nil)
			}
		}
	}

	if !strings.Contains(b.Name, ".") {
		b.p.Command(fmt.Sprintf("runtime! syntax/%s.vim", filetype))
	}

	return b.p.Wait()
}

func (b *Buf) GetBufferContext() error {
	b.p.CurrentBuffer(&b.Buffer)
	b.p.CurrentWindow(&b.Window)
	b.p.CurrentTabpage(&b.Tabpage)

	return b.p.Wait()
}

func (b *Buf) BufferLines(start, end int, strict bool) {
	if b.Buffer == 0 {
		b.GetBufferContext()
	}

	buf, err := b.v.BufferLines(b.Buffer, start, end, strict)
	if err != nil {
		return
	}
	b.Data = ToByteSlice(buf)
}

func (b *Buf) SetBufferLines(start, end int, strict bool, replacement []byte) error {
	if b.Buffer == 0 {
		err := errors.New("Does not exist of target buffer")
		return err
	}

	b.Data = replacement

	return b.v.SetBufferLines(b.Buffer, start, end, strict, ToBufferLines(replacement))
}

func (b *Buf) SetBufferLinesAll(replacement []byte) error {
	if b.Buffer == 0 {
		err := errors.New("Does not exist of target buffer")
		return err
	}

	b.Data = replacement
	b.Write(b.Data)

	return nil
}

// UpdateSyntax updates the syntax highlight of the buffer.
func (b *Buf) UpdateSyntax(syntax string) {
	if b.Name != "" {
		b.v.SetBufferName(b.Buffer, b.Name)
	}

	if syntax == "" {
		var filetype interface{}
		b.v.BufferOption(b.Buffer, "filetype", &filetype)
		syntax = fmt.Sprintf("%s", filetype)
	}

	b.v.Command(fmt.Sprintf("runtime! syntax/%s.vim", syntax))
}

// SetLocalMapping sets buffer local mapping.
// 'mapping' arg: [key]{destination}
func (b *Buf) SetLocalMapping(mode string, mapping map[string]string) error {
	if mapping != nil {
		cwin, err := b.v.CurrentWindow()
		if err != nil {
			return errors.Annotate(err, "nvim/buffer.SetMapping")
		}

		b.p.SetCurrentWindow(b.Window)
		defer b.v.SetCurrentWindow(cwin)

		for k, v := range mapping {
			b.p.Command(fmt.Sprintf("silent %s <buffer><silent>%s %s", mode, k, v))
		}
	}

	return b.p.Wait()
}

// lineCount counts the Neovim buffer line count and check whether 1 count,
// Because new(empty) buffer and one line buffer are both 1 count.
func (b *Buf) lineCount() (int, error) {
	lineCount, err := b.v.BufferLineCount(b.Buffer)
	if err != nil {
		return 0, errors.Annotate(err, pkgBuffer)
	}

	if lineCount == 1 {
		line, err := b.v.CurrentLine()
		if err != nil {
			return 0, errors.Annotate(err, pkgBuffer)
		}
		// Set 0 to lineCount if buffer is empty
		if len(line) == 0 {
			lineCount = 0
		}
	}

	return lineCount, nil
}

// Write appends the contents of p to the Neovim buffer.
func (b *Buf) Write(p []byte) (int, error) {
	lineCount, err := b.lineCount()
	if err != nil {
		return 0, errors.Annotate(err, pkgBuffer)
	}

	buf := bytes.NewBuffer(p)
	b.v.SetBufferLines(b.Buffer, lineCount, -1, true, ToBufferLines(buf.Bytes()))

	return len(p), nil
}

// WriteString appends the contents of s to the Neovim buffer.
func (b *Buf) WriteString(s string) error {
	lineCount, err := b.lineCount()
	if err != nil {
		return errors.Annotate(err, pkgBuffer)
	}

	buf := bytes.NewBufferString(s)

	return b.v.SetBufferLines(b.Buffer, lineCount, -1, true, ToBufferLines(buf.Bytes()))
}

// Truncate discards all but the first n unread bytes from the
// Neovim buffer but continues to use the same allocated storage.
func (b *Buf) Truncate(n int) {
	b.v.SetBufferLines(b.Buffer, n, -1, true, [][]byte{})
}

// Reset resets the Neovim buffer to be empty,
// but it retains the underlying storage for use by future writes.
// Reset is the same as Truncate(0).
func (b *Buf) Reset() { b.Truncate(0) }

// ----------------------------------------------------------------------------
// Utility

// IsBufferValid wrapper of v.IsBufferValid function.
func IsBufferValid(v *vim.Vim, b vim.Buffer) bool {
	res, err := v.IsBufferValid(b)
	if err != nil {
		return false
	}
	return res
}

// BufContains reports whether buffer list is within b.
func IsBufferContains(v *vim.Vim, b vim.Buffer) bool {
	bufs, _ := v.Buffers()
	for _, buf := range bufs {
		if buf == b {
			return true
		}
	}
	return false
}

// BufExists reports whether buffer list is within bufnr use vim bufexists function.
func IsBufExists(v *vim.Vim, bufnr int) bool {
	var res interface{}
	v.Call("bufexists", &res, bufnr)

	return res.(int64) != 0
}

// Modifiable sets modifiable to true,
// The returned function restores modifiable to false.
func Modifiable(v *vim.Vim, b vim.Buffer) func() {
	v.SetBufferOption(b, BufOptionModifiable, true)

	return func() {
		v.SetBufferOption(b, BufOptionModifiable, false)
	}
}

// ToByteSlice converts the 2D buffer byte data to sigle byte slice.
func ToByteSlice(byt [][]byte) []byte { return bytes.Join(byt, []byte{'\n'}) }

// ToBufferLines converts the byte slice to the 2D byte slice of Neovim buffer data.
func ToBufferLines(byt []byte) [][]byte { return bytes.Split(byt, []byte{'\n'}) }

// ByteOffset calculates the byte-offset of current cursor position.
func ByteOffset(v *vim.Vim, b vim.Buffer, w vim.Window) (int, error) {
	cursor, _ := v.WindowCursor(w)
	byteBuf, _ := v.BufferLines(b, 0, -1, true)

	if cursor[0] == 1 {
		return (1 + (cursor[1] - 1)), nil
	}

	var offset int
	line := 1
	for _, buf := range byteBuf {
		if line == cursor[0] {
			offset++
			break
		}
		offset += (binary.Size(buf) + 1)
		line++
	}

	return (offset + (cursor[1] - 1)), nil
}

// ByteOffsetPipe calculates the byte-offset of current cursor position uses vim.Pipeline.
func ByteOffsetPipe(p *vim.Pipeline, b vim.Buffer, w vim.Window) (int, error) {
	var cursor [2]int
	p.WindowCursor(w, &cursor)

	var byteBuf [][]byte
	p.BufferLines(b, 0, -1, true, &byteBuf)

	if err := p.Wait(); err != nil {
		return 0, errors.Annotate(err, "nvim/buffer.ByteOffsetPipe")
	}

	if cursor[0] == 1 {
		return (1 + (cursor[1] - 1)), nil
	}

	offset := 0
	line := 1
	for _, buf := range byteBuf {
		if line == cursor[0] {
			offset++
			break
		}
		offset += (binary.Size(buf) + 1)
		line++
	}

	return (offset + (cursor[1] - 1)), nil
}
<|endoftext|>