<commit_before>package glplus

import (
	"testing"
)

func TestLayout(t *testing.T) {
	// Create our root item. Items are just 2D boxes.
	ctx := Lay_context{}

	// Create our root item. Items are just 2D boxes.
	root := ctx.Lay_item()

	// Let's pretend we have a window in our game or OS of some known dimension.
	// We'll want to explicitly set our root item to be that size.
	ctx.Lay_set_size_xy(root, 1280, 720)

	// Set our root item to arrange its children in a row, left-to-right, in the
	// order they are inserted.
	ctx.Lay_set_contain(root, LAY_ROW)

	// Create the item for our master list.
	master_list := ctx.Lay_item()
	ctx.Lay_insert(root, master_list)

	// Our master list has a specific fixed width, but we want it to fill all
	// available vertical space.
	ctx.Lay_set_size_xy(master_list, 400, 0)
	// We set our item's behavior within its parent to desire filling up available
	// vertical space.
	ctx.Lay_set_behave(master_list, LAY_VFILL)
	// And we set it so that it will lay out its children in a column,
	// top-to-bottom, in the order they are inserted.
	ctx.Lay_set_contain(master_list, LAY_COLUMN)

	content_view := ctx.Lay_item()
	ctx.Lay_insert(root, content_view)
	// The content view just wants to fill up all of the remaining space, so we
	// don't need to set any size on it.
	//
	// We could just set LAY_FILL here instead of bitwise-or'ing LAY_HFILL and
	// LAY_VFILL, but I want to demonstrate that this is how you combine flags.
	ctx.Lay_set_behave(content_view, LAY_HFILL|LAY_VFILL)

	// Normally at this point, we would probably want to create items for our
	// master list and our content view and insert them. This is just a dumb fake
	// example, so let's move on to finishing up.

	// Run the context -- this does all of the actual calculations.
	ctx.Lay_run_context()

	// Now we can get the calculated size of our items as 2D rectangles. The four
	// components of the vector represent x and y of the top left corner, and then
	// the width and height.
	master_list_rect := ctx.Lay_get_rect(master_list)
	content_view_rect := ctx.Lay_get_rect(content_view)

	if (master_list_rect != Lay_vec4{0, 0, 400, 720}) {
		t.Fail()
	}
	if (content_view_rect != Lay_vec4{400, 0, 880, 720}) {
		t.Fail()
	}
}
<commit_msg>more tests<commit_after>package glplus

import (
	"testing"
)

func TestLayout(t *testing.T) {
	// Create our root item. Items are just 2D boxes.
	ctx := Lay_context{}

	// Create our root item. Items are just 2D boxes.
	root := ctx.Lay_item()

	// Let's pretend we have a window in our game or OS of some known dimension.
	// We'll want to explicitly set our root item to be that size.
	ctx.Lay_set_size_xy(root, 1280, 720)

	// Set our root item to arrange its children in a row, left-to-right, in the
	// order they are inserted.
	ctx.Lay_set_contain(root, LAY_ROW)

	// Create the item for our master list.
	master_list := ctx.Lay_item()
	ctx.Lay_insert(root, master_list)

	// Our master list has a specific fixed width, but we want it to fill all
	// available vertical space.
	ctx.Lay_set_size_xy(master_list, 400, 0)
	// We set our item's behavior within its parent to desire filling up available
	// vertical space.
	ctx.Lay_set_behave(master_list, LAY_VFILL)
	// And we set it so that it will lay out its children in a column,
	// top-to-bottom, in the order they are inserted.
	ctx.Lay_set_contain(master_list, LAY_COLUMN)

	content_view := ctx.Lay_item()
	ctx.Lay_insert(root, content_view)
	// The content view just wants to fill up all of the remaining space, so we
	// don't need to set any size on it.
	//
	// We could just set LAY_FILL here instead of bitwise-or'ing LAY_HFILL and
	// LAY_VFILL, but I want to demonstrate that this is how you combine flags.
	ctx.Lay_set_behave(content_view, LAY_HFILL|LAY_VFILL)

	// Normally at this point, we would probably want to create items for our
	// master list and our content view and insert them. This is just a dumb fake
	// example, so let's move on to finishing up.

	// Run the context -- this does all of the actual calculations.
	ctx.Lay_run_context()

	// Now we can get the calculated size of our items as 2D rectangles. The four
	// components of the vector represent x and y of the top left corner, and then
	// the width and height.
	master_list_rect := ctx.Lay_get_rect(master_list)
	content_view_rect := ctx.Lay_get_rect(content_view)

	if (master_list_rect != Lay_vec4{0, 0, 400, 720}) {
		t.Fail()
	}
	if (content_view_rect != Lay_vec4{400, 0, 880, 720}) {
		t.Fail()
	}
}

func LTEST_TRUE(t *testing.T, cond bool) {
	if !cond {
		t.Fail()
	}
}

func TestSimpleFill(t *testing.T) {
	ctx := Lay_context{}
	root := ctx.Lay_item()
	child := ctx.Lay_item()

	ctx.Lay_set_size_xy(root, 30, 40)
	ctx.Lay_set_behave(child, LAY_FILL)
	ctx.Lay_insert(root, child)

	ctx.Lay_run_context()

	root_r := ctx.Lay_get_rect(root)
	child_r := ctx.Lay_get_rect(child)

	LTEST_TRUE(t, root_r[0] == 0 && root_r[1] == 0)
	LTEST_TRUE(t, root_r[2] == 30 && root_r[3] == 40)

	LTEST_TRUE(t, child_r[0] == 0 && child_r[1] == 0)
	LTEST_TRUE(t, child_r[2] == 30 && child_r[3] == 40)

	// Test to make sure size is ok
	root_size := ctx.Lay_get_size(root)
	// Make sure _xy version gives the same values
	root_size_cs := ctx.Lay_get_size_xy(root)
	LTEST_TRUE(t, root_size[0] == 30 && root_size[1] == 40)
	LTEST_TRUE(t, root_size[0] == root_size_cs[0] && root_size[1] == root_size_cs[1])

	// Test to make sure the _xywh getter produces the same results as the
	// ctx.Lay_vec4-return-value version.

	root_r_cs := ctx.Lay_get_rect_xywh(root)
	child_r_cs := ctx.Lay_get_rect_xywh(child)
	LTEST_TRUE(t, root_r[0] == root_r_cs[0] && root_r[1] == root_r_cs[1] && root_r[2] == root_r_cs[2] && root_r[3] == root_r_cs[3])
	LTEST_TRUE(t, child_r[0] == child_r_cs[0] && child_r[1] == child_r_cs[1] && child_r[2] == child_r_cs[2] && child_r[3] == child_r_cs[3])
}
<|endoftext|>


<commit_before>package quadedge_test

import (
	"fmt"
	"math/rand"
	"testing"

	"github.com/drakmaniso/glam"
	"github.com/drakmaniso/glam/colour"
	"github.com/drakmaniso/glam/mouse"
	"github.com/drakmaniso/glam/palette"
	"github.com/drakmaniso/glam/pixel"
	"github.com/drakmaniso/glam/plane"
	"github.com/drakmaniso/glam/plane/quadedge"
)

//------------------------------------------------------------------------------

var screen = pixel.NewCanvas(pixel.Zoom(1))

var cursor = pixel.NewCursor()

func init() {
	cursor.Canvas(screen)
}

var (
	points        []plane.Coord
	triangulation quadedge.Edge
)

//------------------------------------------------------------------------------

var c0 = []plane.Coord{
	{1, 2},
	{1, 2},
	{0, 8},
	{1, 2},
	{0, 8},
	{1, 2},
	{1, 2},
	{0, 8},
}

var four = []plane.Coord{
	{-10, -4},
	{10, -4},
	{0, 10},
	{0, 7.5},
}

func TestDelaunay(t *testing.T) {
	e := quadedge.Delaunay(c0)
	e.Walk(func(f quadedge.Edge) {
		fmt.Println(f.Orig(), "->", f.Dest())
	})
}

//------------------------------------------------------------------------------

func TestDelaunay_graphic(t *testing.T) {
	do(func() {
		err := glam.Run(delLoop{})
		if err != nil {
			t.Error(err)
		}
	})
}

//------------------------------------------------------------------------------

type delLoop struct {
	glam.Handlers
}

//------------------------------------------------------------------------------

func (delLoop) Enter() error {
	points = make([]plane.Coord, 111)
	newPoints()

	palette.Clear()
	palette.Index(1).SetColour(colour.LRGB{0.1, 0.3, 0.6})
	palette.Index(2).SetColour(colour.LRGB{0.8, 0.1, 0.0})
	return nil
}

//------------------------------------------------------------------------------

var (
	ratio  float32
	offset plane.Coord
)

func (delLoop) Draw() error {
	screen.Clear(0)
	ratio = float32(screen.Size().Y)
	offset = plane.Coord{
		X: (float32(screen.Size().X) - ratio) / 2,
		Y: float32(screen.Size().Y),
	}

	m := screen.Mouse()
	p := fromScreen(m)
	cursor.Locate(2, 8, 0x7FFF)
	cursor.ColorShift(0)
	if p.X >= 0 && p.X <= 1.0 {
		cursor.Printf("   %.3f, %.3f\n", p.X, p.Y)
	} else {
		cursor.Println(" ")
	}

	pt := make([]pixel.Coord, len(points))
	l2 := pixel.Coord{2, 2}
	l1 := pixel.Coord{1, 1}
	for i, sd := range points {
		pt[i] = toScreen(sd)
		screen.Lines(2, 0, pt[i].Minus(l2), pt[i].Plus(l2.Perp()),
			pt[i].Plus(l2), pt[i].Minus(l2.Perp()), pt[i].Minus(l2))
		screen.Lines(2, 0, pt[i].Minus(l1), pt[i].Plus(l1.Perp()),
			pt[i].Plus(l1), pt[i].Minus(l1.Perp()), pt[i].Minus(l1))
	}

	triangulation.Walk(func(e quadedge.Edge) {
		screen.Lines(1, 1, toScreen(points[e.Orig()]), toScreen(points[e.Dest()]))
	})

	screen.Display()
	return nil
}

//------------------------------------------------------------------------------

func toScreen(p plane.Coord) pixel.Coord {
	return pixel.Coord{
		X: int16(offset.X + p.X*ratio),
		Y: int16(offset.Y - p.Y*ratio),
	}
}

func fromScreen(p pixel.Coord) plane.Coord {
	return plane.Coord{
		X: (float32(p.X) - offset.X) / ratio,
		Y: (offset.Y - float32(p.Y)) / ratio,
	}
}

//------------------------------------------------------------------------------

func (delLoop) MouseButtonDown(b mouse.Button, _ int) {
	switch b {
	case mouse.Left:
		newPoints()
	case mouse.Right:
		p := fromScreen(screen.Mouse())
		points = append(points, p)
		triangulation = quadedge.Delaunay(points)
	}
}

//------------------------------------------------------------------------------

func newPoints() {
	for i := range points {
		points[i] = plane.Coord{X: rand.Float32(), Y: rand.Float32()}
	}
	triangulation = quadedge.Delaunay(points)
}

//------------------------------------------------------------------------------
<commit_msg>Add a few tests for quadedge.Delaunay<commit_after>package quadedge_test

import (
	"github.com/drakmaniso/glam/x/math32"
	"fmt"
	"math/rand"
	"testing"

	"github.com/drakmaniso/glam/key"

	"github.com/drakmaniso/glam"
	"github.com/drakmaniso/glam/colour"
	"github.com/drakmaniso/glam/mouse"
	"github.com/drakmaniso/glam/palette"
	"github.com/drakmaniso/glam/pixel"
	"github.com/drakmaniso/glam/plane"
	"github.com/drakmaniso/glam/plane/quadedge"
)

//------------------------------------------------------------------------------

var screen = pixel.NewCanvas(pixel.Zoom(1))

var cursor = pixel.NewCursor()

func init() {
	cursor.Canvas(screen)
}

var (
	points        []plane.Coord
	triangulation quadedge.Edge
)

//------------------------------------------------------------------------------

var c0 = []plane.Coord{
	{1, 2},
	{1, 2},
	{0, 8},
	{1, 2},
	{0, 8},
	{1, 2},
	{1, 2},
	{0, 8},
}

var four = []plane.Coord{
	{-10, -4},
	{10, -4},
	{0, 10},
	{0, 7.5},
}

func TestDelaunay(t *testing.T) {
	e := quadedge.Delaunay(c0)
	e.Walk(func(f quadedge.Edge) {
		fmt.Println(f.Orig(), "->", f.Dest())
	})
}

//------------------------------------------------------------------------------

func TestDelaunay_graphic(t *testing.T) {
	do(func() {
		err := glam.Run(delLoop{})
		if err != nil {
			t.Error(err)
		}
	})
}

//------------------------------------------------------------------------------

type delLoop struct {
	glam.Handlers
}

//------------------------------------------------------------------------------

func (delLoop) Enter() error {
	points = make([]plane.Coord, 111)
	newPoints()

	palette.Clear()
	palette.Index(1).SetColour(colour.LRGB{0.1, 0.3, 0.6})
	palette.Index(2).SetColour(colour.LRGB{0.8, 0.1, 0.0})
	return nil
}

//------------------------------------------------------------------------------

var (
	ratio  float32
	offset plane.Coord
)

func (delLoop) Draw() error {
	screen.Clear(0)
	ratio = float32(screen.Size().Y)
	offset = plane.Coord{
		X: (float32(screen.Size().X) - ratio) / 2,
		Y: float32(screen.Size().Y),
	}

	m := screen.Mouse()
	p := fromScreen(m)
	cursor.Locate(2, 8, 0x7FFF)
	cursor.ColorShift(0)
	if p.X >= 0 && p.X <= 1.0 {
		cursor.Printf("   %.3f, %.3f\n", p.X, p.Y)
	} else {
		cursor.Println(" ")
	}

	pt := make([]pixel.Coord, len(points))
	l2 := pixel.Coord{2, 2}
	l1 := pixel.Coord{1, 1}
	for i, sd := range points {
		pt[i] = toScreen(sd)
		screen.Lines(2, 0, pt[i].Minus(l2), pt[i].Plus(l2.Perp()),
			pt[i].Plus(l2), pt[i].Minus(l2.Perp()), pt[i].Minus(l2))
		screen.Lines(2, 0, pt[i].Minus(l1), pt[i].Plus(l1.Perp()),
			pt[i].Plus(l1), pt[i].Minus(l1.Perp()), pt[i].Minus(l1))
	}

	triangulation.Walk(func(e quadedge.Edge) {
		screen.Lines(1, 1, toScreen(points[e.Orig()]), toScreen(points[e.Dest()]))
	})

	screen.Display()
	return nil
}

//------------------------------------------------------------------------------

func toScreen(p plane.Coord) pixel.Coord {
	return pixel.Coord{
		X: int16(offset.X + p.X*ratio),
		Y: int16(offset.Y - p.Y*ratio),
	}
}

func fromScreen(p pixel.Coord) plane.Coord {
	return plane.Coord{
		X: (float32(p.X) - offset.X) / ratio,
		Y: (offset.Y - float32(p.Y)) / ratio,
	}
}

//------------------------------------------------------------------------------

func (delLoop) MouseButtonDown(b mouse.Button, _ int) {
	switch b {
	case mouse.Left:
		newPoints()
	case mouse.Right:
		p := fromScreen(screen.Mouse())
		points = append(points, p)
		triangulation = quadedge.Delaunay(points)
	}
}

//------------------------------------------------------------------------------

func newPoints() {
	for i := range points {
		points[i] = plane.Coord{X: rand.Float32(), Y: rand.Float32()}
	}
	triangulation = quadedge.Delaunay(points)
}

//------------------------------------------------------------------------------

func (dl delLoop) KeyDown(l key.Label, p key.Position) {
	switch l {
	case key.Label1:
		points = points[:0]
		const st = 1.0 / 16
		for x := float32(st); x < 1.0; x += st {
			for y := float32(st); y < 1.0; y += st {
				points = append(points, plane.Coord{X: x, Y: y})
			}
		}
		triangulation = quadedge.Delaunay(points)
	case key.Label2:
		points = points[:0]
		for a := float32(0); a < 2*math32.Pi; a += math32.Pi/8 {
				points = append(points, plane.Coord{
					X: .5-math32.Cos(a)*.5,
					Y: .5+math32.Sin(a)*.5,
				})
		}
		triangulation = quadedge.Delaunay(points)
	case key.Label3:
		points = points[:0]
		const n = 26
		for a := float32(0); a < n*2*math32.Pi; a += math32.Pi/26 {
				points = append(points, plane.Coord{
					X: .5+math32.Cos(a)*.5*a/(n*2*math32.Pi),
					Y: .5+math32.Sin(a)*.5*a/(n*2*math32.Pi),
				})
		}
		triangulation = quadedge.Delaunay(points)
	case key.Label4:
		points = points[:0]
		const st = 1.0 / 6
		for x := float32(st); x < 1.0; x += st {
				points = append(points, plane.Coord{X: x, Y: 0.5 + 0.17*x})
		}
		triangulation = quadedge.Delaunay(points)
	default:
		dl.Handlers.KeyDown(l, p)
	}
}

//------------------------------------------------------------------------------
<|endoftext|>


<commit_before>package bugsnag

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/grokify/chathooks/src/config"
	"github.com/grokify/chathooks/src/handlers"
	"github.com/grokify/chathooks/src/models"
	cc "github.com/grokify/commonchat"
	log "github.com/sirupsen/logrus"
)

const (
	DisplayName      = "Bugsnag"
	HandlerKey       = "bugsnag"
	MessageDirection = "out"
	MessageBodyType  = models.JSON
)

func NewHandler() handlers.Handler {
	return handlers.Handler{MessageBodyType: MessageBodyType, Normalize: Normalize}
}

/*
Bugsnag

**New error** in **production** from [Webpage]() in example:activity ([details]())

Error
ExampleError: An error occurred

Location
controllers/example.rb:11 - example_call
*/

func Normalize(cfg config.Configuration, bytes []byte) (cc.Message, error) {
	ccMsg := cc.NewMessage()
	iconURL, err := cfg.GetAppIconURL(HandlerKey)
	if err == nil {
		ccMsg.IconURL = iconURL.String()
	}

	ccMsg.Activity = fmt.Sprintf("%s alert", DisplayName)

	src, err := BugsnagOutMessageFromBytes(bytes)
	if err != nil {
		return ccMsg, err
	}

	textMessage := buildTextMessage(src)
	if len(textMessage) > 0 {
		ccMsg.Title = textMessage
	}

	fields := []cc.Field{}

	if len(strings.TrimSpace(src.Error.Message)) > 0 {
		fields = append(fields, cc.Field{
			Title: "Error",
			Value: src.Error.Message})
	}

	for _, st := range src.Error.StackTrace {
		location := strings.TrimSpace(st.Location())
		if len(location) > 0 {
			fields = append(fields, cc.Field{
				Title: "Location",
				Value: location})
			break
		}
	}

	if len(strings.TrimSpace(src.Error.Status)) > 0 {
		status := src.Error.Status
		if strings.ToLower(strings.TrimSpace(status)) == "open" {
			unhandled := src.Error.Unhandled
			if unhandled {
				status += " - unhandled"
			} else {
				status += " - handled"
			}
		}

		if len(src.Error.Url) > 0 {
			status = fmt.Sprintf("[%s](%s)", status, src.Error.Url)
		}

		fields = append(fields, cc.Field{
			Title: "Status",
			Value: status})
	}

	if len(fields) > 0 {
		attachment := cc.NewAttachment()
		for _, field := range fields {
			attachment.AddField(field)
		}
		ccMsg.AddAttachment(attachment)
	}

	return ccMsg, nil
}

func buildTextMessage(src BugsnagOutMessage) string {
	parts := []string{}

	triggerMsg := strings.TrimSpace(src.Trigger.Message)
	if len(triggerMsg) > 0 {
		parts = append(parts, fmt.Sprintf("**%s**", triggerMsg))
	}

	stage := strings.TrimSpace(src.Release.ReleaseStage)
	if len(stage) > 0 {
		parts = append(parts, fmt.Sprintf("in **%s**", stage))
	}

	projLink := src.Project.MarkdownLink()
	if len(projLink) > 0 {
		parts = append(parts, fmt.Sprintf("from **%s**", projLink))
	}

	if len(src.Error.Context) > 0 {
		parts = append(parts, fmt.Sprintf("in %s", src.Error.Context))
	}

	if len(src.Error.Url) > 0 {
		details := fmt.Sprintf("([details](%s))", src.Error.Url)
		parts = append(parts, details)
	}

	return strings.Join(parts, " ")
}

func BugsnagOutMessageFromBytes(bytes []byte) (BugsnagOutMessage, error) {
	log.WithFields(log.Fields{
		"type":    "message.raw",
		"message": string(bytes),
	}).Debug(fmt.Sprintf("%v message.", DisplayName))
	msg := BugsnagOutMessage{}
	err := json.Unmarshal(bytes, &msg)
	if err != nil {
		log.WithFields(log.Fields{
			"type":  "message.json.unmarshal",
			"error": fmt.Sprintf("%v\n", err),
		}).Warn(fmt.Sprintf("%v request unmarshal failure.", DisplayName))
	}
	return msg, err
}

type BugsnagOutMessage struct {
	Account BugsnagAccount `json:"account,omitempty"`
	Project BugsnagProject `json:"project,omitempty"`
	Trigger BugsnagTrigger `json:"trigger,omitempty"`
	Comment BugsnagComment `json:"created_at,omitempty"`
	User    BugsnagUser    `json:"user,omitempty"`
	Error   BugsnagError   `json:"error,omitempty"`
	Release BugsnagRelease `json:"release,omitempty"`
}

func (msg *BugsnagOutMessage) ReleaseStage() string {
	relRelStage := strings.TrimSpace(msg.Release.ReleaseStage)
	appRelStage := strings.TrimSpace(msg.Error.App.ReleaseStage)
	if len(appRelStage) > 0 {
		return appRelStage
	}
	return relRelStage
}

type BugsnagAccount struct {
	Id   string `json:"id,omitempty"`
	Name string `json:"name,omitempty"`
	URL  string `json:"url,omitempty"`
}

type BugsnagProject struct {
	Id   string `json:"id,omitempty"`
	Name string `json:"name,omitempty"`
	URL  string `json:"url,omitempty"`
}

func (bp *BugsnagProject) MarkdownLink() string {
	if len(bp.Name) > 0 && len(bp.URL) > 0 {
		return fmt.Sprintf("[%s](%s)", bp.Name, bp.URL)
	} else if len(bp.URL) > 0 {
		return fmt.Sprintf("[%s](%s)", bp.URL, bp.URL)
	}
	return ""
}

type BugsnagTrigger struct {
	Type        string            `json:"type,omitempty"`
	Message     string            `json:"message,omitempty"`
	SnoozeRule  BugsnagSnoozeRule `json:"snoozeRule,omitempty"`
	Rate        int32             `json:"rate,omitempty"`
	StateChange string            `json:"stateChange,omitempty"`
}

type BugsnagSnoozeRule struct {
	Type      string `json:"type,omitempty"`
	RuleValue int32  `json:"ruleValue,omitempty"`
}

type BugsnagComment struct{}

type BugsnagUser struct {
	Id    string `json:"id,omitempty"`
	Name  string `json:"name,omitempty"`
	Email string `json:"email,omitempty"`
}

type BugsnagRelease struct {
	Id            string               `json:"id,omitempty"`
	Version       string               `json:"version,omitempty"`
	VersionCode   string               `json:"versionCode,omitempty"`
	BundleVersion string               `json:"bundleVersion,omitempty"`
	ReleaseStage  string               `json:"releaseStage,omitempty"`
	URL           string               `json:"url,omitempty"`
	ReleaseTime   time.Time            `json:"releaseTime,omitempty"`
	ReleasedBy    string               `json:"releasedBy,omitempty"`
	SourceControl BugsnagSourceControl `json:"sourceControl,omitempty"`
	Metadata      map[string]string    `json:"metadata,omitempty"`
}

type BugsnagSourceControl struct {
	Provider    string `json:"provider,omitempty"`
	Revision    string `json:"revision,omitempty"`
	RevisionURL string `json:"revisionUrl,omitempty"`
	DiffURL     string `json:"diffUrl,omitempty"`
}

type BugsnagError struct {
	Id             string                   `json:"id,omitempty"`
	ErrorId        string                   `json:"errorId,omitempty"`
	ExceptionClass string                   `json:"exceptionClass,omitempty"`
	Message        string                   `json:"message,omitempty"`
	Context        string                   `json:"context,omitempty"`
	FirstReceived  time.Time                `json:"firstReceived,omitempty"`
	ReceivedAt     time.Time                `json:"receivedAt,omitempty"`
	RequestUrl     string                   `json:"requestUrl,omitempty"`
	AssignedUserId string                   `json:"assignedUserId,omitempty"`
	Url            string                   `json:"url,omitempty"`
	Severity       string                   `json:"severity,omitempty"`
	Status         string                   `json:"status,omitempty"`
	Unhandled      bool                     `json:"unhandled,omitempty"`
	CreatedIssue   BugsnagErrorIssue        `json:"createdIssue,omitempty"`
	User           BugsnagUser              `json:"user,omitempty"`
	App            BugsnaggErrorApp         `json:"app,omitempty"`
	Device         BugsnagErrorDevice       `json:"device,omitempty"`
	StackTrace     []BugsnagErrorStackTrace `json:"stackTrace,omitempty"`
}

type BugsnagErrorIssue struct {
	Id     string `json:"id,omitempty"`
	Number int    `json:"name,omitempty"`
	Type   string `json:"type,omitempty"`
	URL    string `json:"url,omitempty"`
}

type BugsnaggErrorApp struct {
	Id                   string   `json:"id,omitempty"`
	Version              string   `json:"version,omitempty"`
	VersionCode          string   `json:"versionCode,omitempty"`
	BundleVersion        string   `json:"bundleVersion,omitempty"`
	CodeBundleId         string   `json:"codeBundleId,omitempty"`
	BuildUUID            string   `json:"buildUUID,omitempty"`
	ReleaseStage         string   `json:"releaseStage,omitempty"`
	Type                 string   `json:"type,omitempty"`
	DsymUUIDs            []string `json:"dsymUUIDs,omitempty"`
	Duration             int      `json:"duration,omitempty"`
	durationInForeground int      `json:"durationInForeground,omitempty"`
	inForeground         bool     `json:"inForeground,omitempty"`
}

type BugsnagErrorDevice struct {
	Id             string    `json:"id,omitempty"`
	Manufacturer   string    `json:"manufacturer,omitempty"`
	Model          string    `json:"model,omitempty"`
	ModelNumber    string    `json:"modelNumber,omitempty"`
	OsName         string    `json:"osName,omitempty"`
	OsVersion      string    `json:"osVersion,omitempty"`
	FreeMemory     int       `json:"freeMemory,omitempty"`
	TotalMemory    int       `json:"totalMemory,omitempty"`
	FreeDisk       int       `json:"freeDisk,omitempty"`
	BrowserName    string    `json:"browserName,omitempty"`
	BrowserVersion string    `json:"browserVersion,omitempty"`
	Jailbroken     bool      `json:"jailbroken,omitempty"`
	Orientation    string    `json:"orientation,omitempty"`
	Locale         string    `json:"locale,omitempty"`
	Charging       bool      `json:"charging,omitempty"`
	BatteryLevel   float32   `json:"batteryLevel,omitempty"`
	Time           time.Time `json:"time,omitempty"`
	Timezone       string    `json:"timezone,omitempty"`
}

type BugsnagErrorStackTrace struct {
	InProject    bool              `json:"inProject,omitempty"`
	LineNumber   int               `json:"lineNumber,omitempty"`
	ColumnNumber int               `json:"columnNumber,omitempty"`
	File         string            `json:"file,omitempty"`
	Method       string            `json:"method,omitempty"`
	Code         map[string]string `json:"code,omitempty"`
}

// Location returns a string per the Slack integration
func (st *BugsnagErrorStackTrace) Location() string {
	location := ""
	st.File = strings.TrimSpace(st.File)
	st.Method = strings.TrimSpace(st.Method)
	if len(st.File) > 0 {
		location = st.File
		if st.LineNumber > 0 {
			location += ":" + strconv.Itoa(st.LineNumber)
		}
		if len(st.Method) > 0 {
			location += " - " + st.Method
		}
	}
	return location
}
<commit_msg>update bugsnag stackTrace support<commit_after>package bugsnag

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

	"github.com/grokify/chathooks/src/config"
	"github.com/grokify/chathooks/src/handlers"
	"github.com/grokify/chathooks/src/models"
	cc "github.com/grokify/commonchat"
	log "github.com/sirupsen/logrus"
)

const (
	DisplayName      = "Bugsnag"
	HandlerKey       = "bugsnag"
	MessageDirection = "out"
	MessageBodyType  = models.JSON
)

func NewHandler() handlers.Handler {
	return handlers.Handler{MessageBodyType: MessageBodyType, Normalize: Normalize}
}

/*
Bugsnag

**New error** in **production** from [Webpage]() in example:activity ([details]())

Error
ExampleError: An error occurred

Location
controllers/example.rb:11 - example_call
*/

func Normalize(cfg config.Configuration, bytes []byte) (cc.Message, error) {
	ccMsg := cc.NewMessage()
	iconURL, err := cfg.GetAppIconURL(HandlerKey)
	if err == nil {
		ccMsg.IconURL = iconURL.String()
	}

	ccMsg.Activity = fmt.Sprintf("%s alert", DisplayName)

	src, err := BugsnagOutMessageFromBytes(bytes)
	if err != nil {
		return ccMsg, err
	}

	textMessage := buildTextMessage(src)
	if len(textMessage) > 0 {
		ccMsg.Title = textMessage
	}

	fields := []cc.Field{}

	if len(strings.TrimSpace(src.Error.Message)) > 0 {
		fields = append(fields, cc.Field{
			Title: "Error",
			Value: src.Error.Message})
	}

	for _, st := range src.Error.StackTrace {
		location := strings.TrimSpace(st.Location())
		if len(location) > 0 {
			fields = append(fields, cc.Field{
				Title: "Location",
				Value: location})
			break
		}
	}

	if len(strings.TrimSpace(src.Error.Status)) > 0 {
		status := src.Error.Status
		if strings.ToLower(strings.TrimSpace(status)) == "open" {
			unhandled := src.Error.Unhandled
			if unhandled {
				status += " - unhandled"
			} else {
				status += " - handled"
			}
		}

		if len(src.Error.Url) > 0 {
			status = fmt.Sprintf("[%s](%s)", status, src.Error.Url)
		}

		fields = append(fields, cc.Field{
			Title: "Status",
			Value: status})
	}

	if len(fields) > 0 {
		attachment := cc.NewAttachment()
		for _, field := range fields {
			attachment.AddField(field)
		}
		ccMsg.AddAttachment(attachment)
	}

	return ccMsg, nil
}

func buildTextMessage(src BugsnagOutMessage) string {
	parts := []string{}

	triggerMsg := strings.TrimSpace(src.Trigger.Message)
	if len(triggerMsg) > 0 {
		parts = append(parts, fmt.Sprintf("**%s**", triggerMsg))
	}

	stage := strings.TrimSpace(src.Release.ReleaseStage)
	if len(stage) > 0 {
		parts = append(parts, fmt.Sprintf("in **%s**", stage))
	}

	projLink := src.Project.MarkdownLink()
	if len(projLink) > 0 {
		parts = append(parts, fmt.Sprintf("from **%s**", projLink))
	}

	if len(src.Error.Context) > 0 {
		parts = append(parts, fmt.Sprintf("in %s", src.Error.Context))
	}

	if len(src.Error.Url) > 0 {
		details := fmt.Sprintf("([details](%s))", src.Error.Url)
		parts = append(parts, details)
	}

	return strings.Join(parts, " ")
}

func BugsnagOutMessageFromBytes(bytes []byte) (BugsnagOutMessage, error) {
	log.WithFields(log.Fields{
		"type":    "message.raw",
		"message": string(bytes),
	}).Debug(fmt.Sprintf("%v message.", DisplayName))
	msg := BugsnagOutMessage{}
	err := json.Unmarshal(bytes, &msg)
	if err != nil {
		log.WithFields(log.Fields{
			"type":  "message.json.unmarshal",
			"error": fmt.Sprintf("%v\n", err),
		}).Warn(fmt.Sprintf("%v request unmarshal failure.", DisplayName))
	}
	return msg, err
}

type BugsnagOutMessage struct {
	Account BugsnagAccount `json:"account,omitempty"`
	Project BugsnagProject `json:"project,omitempty"`
	Trigger BugsnagTrigger `json:"trigger,omitempty"`
	Comment BugsnagComment `json:"created_at,omitempty"`
	User    BugsnagUser    `json:"user,omitempty"`
	Error   BugsnagError   `json:"error,omitempty"`
	Release BugsnagRelease `json:"release,omitempty"`
}

func (msg *BugsnagOutMessage) ReleaseStage() string {
	relRelStage := strings.TrimSpace(msg.Release.ReleaseStage)
	appRelStage := strings.TrimSpace(msg.Error.App.ReleaseStage)
	if len(appRelStage) > 0 {
		return appRelStage
	}
	return relRelStage
}

type BugsnagAccount struct {
	Id   string `json:"id,omitempty"`
	Name string `json:"name,omitempty"`
	URL  string `json:"url,omitempty"`
}

type BugsnagProject struct {
	Id   string `json:"id,omitempty"`
	Name string `json:"name,omitempty"`
	URL  string `json:"url,omitempty"`
}

func (bp *BugsnagProject) MarkdownLink() string {
	if len(bp.Name) > 0 && len(bp.URL) > 0 {
		return fmt.Sprintf("[%s](%s)", bp.Name, bp.URL)
	} else if len(bp.URL) > 0 {
		return fmt.Sprintf("[%s](%s)", bp.URL, bp.URL)
	}
	return ""
}

type BugsnagTrigger struct {
	Type        string            `json:"type,omitempty"`
	Message     string            `json:"message,omitempty"`
	SnoozeRule  BugsnagSnoozeRule `json:"snoozeRule,omitempty"`
	Rate        int32             `json:"rate,omitempty"`
	StateChange string            `json:"stateChange,omitempty"`
}

type BugsnagSnoozeRule struct {
	Type      string `json:"type,omitempty"`
	RuleValue int32  `json:"ruleValue,omitempty"`
}

type BugsnagComment struct{}

type BugsnagUser struct {
	Id    string `json:"id,omitempty"`
	Name  string `json:"name,omitempty"`
	Email string `json:"email,omitempty"`
}

type BugsnagRelease struct {
	Id            string               `json:"id,omitempty"`
	Version       string               `json:"version,omitempty"`
	VersionCode   string               `json:"versionCode,omitempty"`
	BundleVersion string               `json:"bundleVersion,omitempty"`
	ReleaseStage  string               `json:"releaseStage,omitempty"`
	URL           string               `json:"url,omitempty"`
	ReleaseTime   time.Time            `json:"releaseTime,omitempty"`
	ReleasedBy    string               `json:"releasedBy,omitempty"`
	SourceControl BugsnagSourceControl `json:"sourceControl,omitempty"`
	Metadata      map[string]string    `json:"metadata,omitempty"`
}

type BugsnagSourceControl struct {
	Provider    string `json:"provider,omitempty"`
	Revision    string `json:"revision,omitempty"`
	RevisionURL string `json:"revisionUrl,omitempty"`
	DiffURL     string `json:"diffUrl,omitempty"`
}

type BugsnagError struct {
	Id             string                   `json:"id,omitempty"`
	ErrorId        string                   `json:"errorId,omitempty"`
	ExceptionClass string                   `json:"exceptionClass,omitempty"`
	Message        string                   `json:"message,omitempty"`
	Context        string                   `json:"context,omitempty"`
	FirstReceived  time.Time                `json:"firstReceived,omitempty"`
	ReceivedAt     time.Time                `json:"receivedAt,omitempty"`
	RequestUrl     string                   `json:"requestUrl,omitempty"`
	AssignedUserId string                   `json:"assignedUserId,omitempty"`
	Url            string                   `json:"url,omitempty"`
	Severity       string                   `json:"severity,omitempty"`
	Status         string                   `json:"status,omitempty"`
	Unhandled      bool                     `json:"unhandled,omitempty"`
	CreatedIssue   BugsnagErrorIssue        `json:"createdIssue,omitempty"`
	User           BugsnagUser              `json:"user,omitempty"`
	App            BugsnaggErrorApp         `json:"app,omitempty"`
	Device         BugsnagErrorDevice       `json:"device,omitempty"`
	StackTrace     []BugsnagErrorStackTrace `json:"stackTrace,omitempty"`
}

type BugsnagErrorIssue struct {
	Id     string `json:"id,omitempty"`
	Number int    `json:"name,omitempty"`
	Type   string `json:"type,omitempty"`
	URL    string `json:"url,omitempty"`
}

type BugsnaggErrorApp struct {
	Id                   string   `json:"id,omitempty"`
	Version              string   `json:"version,omitempty"`
	VersionCode          string   `json:"versionCode,omitempty"`
	BundleVersion        string   `json:"bundleVersion,omitempty"`
	CodeBundleId         string   `json:"codeBundleId,omitempty"`
	BuildUUID            string   `json:"buildUUID,omitempty"`
	ReleaseStage         string   `json:"releaseStage,omitempty"`
	Type                 string   `json:"type,omitempty"`
	DsymUUIDs            []string `json:"dsymUUIDs,omitempty"`
	Duration             int      `json:"duration,omitempty"`
	durationInForeground int      `json:"durationInForeground,omitempty"`
	inForeground         bool     `json:"inForeground,omitempty"`
}

type BugsnagErrorDevice struct {
	Id             string    `json:"id,omitempty"`
	Manufacturer   string    `json:"manufacturer,omitempty"`
	Model          string    `json:"model,omitempty"`
	ModelNumber    string    `json:"modelNumber,omitempty"`
	OsName         string    `json:"osName,omitempty"`
	OsVersion      string    `json:"osVersion,omitempty"`
	FreeMemory     int       `json:"freeMemory,omitempty"`
	TotalMemory    int       `json:"totalMemory,omitempty"`
	FreeDisk       int       `json:"freeDisk,omitempty"`
	BrowserName    string    `json:"browserName,omitempty"`
	BrowserVersion string    `json:"browserVersion,omitempty"`
	Jailbroken     bool      `json:"jailbroken,omitempty"`
	Orientation    string    `json:"orientation,omitempty"`
	Locale         string    `json:"locale,omitempty"`
	Charging       bool      `json:"charging,omitempty"`
	BatteryLevel   float32   `json:"batteryLevel,omitempty"`
	Time           time.Time `json:"time,omitempty"`
	Timezone       string    `json:"timezone,omitempty"`
}

type BugsnagErrorStackTrace struct {
	InProject    bool              `json:"inProject,omitempty"`
	LineNumber   json.Number       `json:"lineNumber,omitempty"`
	ColumnNumber json.Number       `json:"columnNumber,omitempty"`
	File         string            `json:"file,omitempty"`
	Method       string            `json:"method,omitempty"`
	Code         map[string]string `json:"code,omitempty"`
}

// Location returns a string per the Slack integration
func (st *BugsnagErrorStackTrace) Location() string {
	location := ""
	st.File = strings.TrimSpace(st.File)
	st.Method = strings.TrimSpace(st.Method)
	if len(st.File) > 0 {
		location = st.File
		if len(st.LineNumber.String()) > 0 {
			location += ":" + st.LineNumber.String()
		}
		if len(st.Method) > 0 {
			location += " - " + st.Method
		}
	}
	return location
}
<|endoftext|>


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

package main

import (
	"bytes"
	"flag"
	"fmt"
	"go/build"
	"io"
	"log"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"strings"
	"sync"
	"text/template"
	"unicode"
	"unicode/utf8"
)

// A Command is an implementation of a go command
// like go build or go fix.
type Command struct {
	// Run runs the command.
	// The args are the arguments after the command name.
	Run func(cmd *Command, args []string)

	// UsageLine is the one-line usage message.
	// The first word in the line is taken to be the command name.
	UsageLine string

	// Short is the short description shown in the 'go help' output.
	Short string

	// Long is the long message shown in the 'go help <this-command>' output.
	Long string

	// Flag is a set of flags specific to this command.
	Flag flag.FlagSet

	// CustomFlags indicates that the command will do its own
	// flag parsing.
	CustomFlags bool
}

// Name returns the command's name: the first word in the usage line.
func (c *Command) Name() string {
	name := c.UsageLine
	i := strings.Index(name, " ")
	if i >= 0 {
		name = name[:i]
	}
	return name
}

func (c *Command) Usage() {
	fmt.Fprintf(os.Stderr, "usage: %s\n\n", c.UsageLine)
	fmt.Fprintf(os.Stderr, "%s\n", strings.TrimSpace(c.Long))
	os.Exit(2)
}

// Runnable reports whether the command can be run; otherwise
// it is a documentation pseudo-command such as importpath.
func (c *Command) Runnable() bool {
	return c.Run != nil
}

// Commands lists the available commands and help topics.
// The order here is the order in which they are printed by 'go help'.
var commands = []*Command{
	cmdBuild,
	cmdClean,
	cmdDoc,
	cmdFix,
	cmdFmt,
	cmdGet,
	cmdInstall,
	cmdList,
	cmdRun,
	cmdTest,
	cmdTool,
	cmdVersion,
	cmdVet,

	helpGopath,
	helpImportpath,
	helpRemote,
	helpTestflag,
	helpTestfunc,
}

var exitStatus = 0
var exitMu sync.Mutex

func setExitStatus(n int) {
	exitMu.Lock()
	if exitStatus < n {
		exitStatus = n
	}
	exitMu.Unlock()
}

func main() {
	flag.Usage = usage
	flag.Parse()
	log.SetFlags(0)

	args := flag.Args()
	if len(args) < 1 {
		usage()
	}

	if args[0] == "help" {
		help(args[1:])
		return
	}

	for _, cmd := range commands {
		if cmd.Name() == args[0] && cmd.Run != nil {
			cmd.Flag.Usage = func() { cmd.Usage() }
			if cmd.CustomFlags {
				args = args[1:]
			} else {
				cmd.Flag.Parse(args[1:])
				args = cmd.Flag.Args()
			}
			cmd.Run(cmd, args)
			exit()
			return
		}
	}

	fmt.Fprintf(os.Stderr, "Unknown command %#q\n\n", args[0])
	usage()
}

var usageTemplate = `Go is a tool for managing Go source code.

Usage: go command [arguments]

The commands are:
{{range .}}{{if .Runnable}}
    {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}

Use "go help [command]" for more information about a command.

Additional help topics:
{{range .}}{{if not .Runnable}}
    {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}

Use "go help [topic]" for more information about that topic.

`

var helpTemplate = `{{if .Run}}usage: go {{.UsageLine}}

{{end}}{{.Long | trim}}
`

var documentationTemplate = `// Copyright 2011 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
{{range .}}{{if .Short}}{{.Short | capitalize}}

{{end}}{{if .Run}}Usage:

	go {{.UsageLine}}

{{end}}{{.Long | trim}}

{{end}}*/
package documentation

// NOTE: cmdDoc is in fmt.go.
`

// tmpl executes the given template text on data, writing the result to w.
func tmpl(w io.Writer, text string, data interface{}) {
	t := template.New("top")
	t.Funcs(template.FuncMap{"trim": strings.TrimSpace, "capitalize": capitalize})
	template.Must(t.Parse(text))
	if err := t.Execute(w, data); err != nil {
		panic(err)
	}
}

func capitalize(s string) string {
	if s == "" {
		return s
	}
	r, n := utf8.DecodeRuneInString(s)
	return string(unicode.ToTitle(r)) + s[n:]
}

func printUsage(w io.Writer) {
	tmpl(w, usageTemplate, commands)
}

func usage() {
	printUsage(os.Stderr)
	os.Exit(2)
}

// help implements the 'help' command.
func help(args []string) {
	if len(args) == 0 {
		printUsage(os.Stdout)
		// not exit 2: succeeded at 'go help'.
		return
	}
	if len(args) != 1 {
		fmt.Fprintf(os.Stderr, "usage: go help command\n\nToo many arguments given.\n")
		os.Exit(2) // failed at 'go help'
	}

	arg := args[0]

	// 'go help documentation' generates doc.go.
	if arg == "documentation" {
		buf := new(bytes.Buffer)
		printUsage(buf)
		usage := &Command{Long: buf.String()}
		tmpl(os.Stdout, documentationTemplate, append([]*Command{usage}, commands...))
		return
	}

	for _, cmd := range commands {
		if cmd.Name() == arg {
			tmpl(os.Stdout, helpTemplate, cmd)
			// not exit 2: succeeded at 'go help cmd'.
			return
		}
	}

	fmt.Fprintf(os.Stderr, "Unknown help topic %#q.  Run 'go help'.\n", arg)
	os.Exit(2) // failed at 'go help cmd'
}

// importPaths returns the import paths to use for the given command line.
func importPaths(args []string) []string {
	if len(args) == 0 {
		return []string{"."}
	}
	var out []string
	for _, a := range args {
		if isLocalPath(a) && strings.Contains(a, "...") {
			out = append(out, allPackagesInFS(a)...)
			continue
		}
		if a == "all" || a == "std" || strings.Contains(a, "...") {
			out = append(out, allPackages(a)...)
			continue
		}
		out = append(out, a)
	}
	return out
}

var atexitFuncs []func()

func atexit(f func()) {
	atexitFuncs = append(atexitFuncs, f)
}

func exit() {
	for _, f := range atexitFuncs {
		f()
	}
	os.Exit(exitStatus)
}

func fatalf(format string, args ...interface{}) {
	errorf(format, args...)
	exit()
}

func errorf(format string, args ...interface{}) {
	log.Printf(format, args...)
	setExitStatus(1)
}

var logf = log.Printf

func exitIfErrors() {
	if exitStatus != 0 {
		exit()
	}
}

func run(cmdargs ...interface{}) {
	cmdline := stringList(cmdargs...)
	cmd := exec.Command(cmdline[0], cmdline[1:]...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		errorf("%v", err)
	}
}

func runOut(dir string, cmdargs ...interface{}) []byte {
	cmdline := stringList(cmdargs...)
	cmd := exec.Command(cmdline[0], cmdline[1:]...)
	cmd.Dir = dir
	out, err := cmd.CombinedOutput()
	if err != nil {
		os.Stderr.Write(out)
		errorf("%v", err)
		out = nil
	}
	return out
}

// matchPattern(pattern)(name) reports whether
// name matches pattern.  Pattern is a limited glob
// pattern in which '...' means 'any string' and there
// is no other special syntax.
func matchPattern(pattern string) func(name string) bool {
	re := regexp.QuoteMeta(pattern)
	re = strings.Replace(re, `\.\.\.`, `.*`, -1)
	reg := regexp.MustCompile(`^` + re + `$`)
	return func(name string) bool {
		return reg.MatchString(name)
	}
}

// allPackages returns all the packages that can be found
// under the $GOPATH directories and $GOROOT matching pattern.
// The pattern is either "all" (all packages), "std" (standard packages)
// or a path including "...".
func allPackages(pattern string) []string {
	match := func(string) bool { return true }
	if pattern != "all" && pattern != "std" {
		match = matchPattern(pattern)
	}

	have := map[string]bool{
		"builtin": true, // ignore pseudo-package that exists only for documentation
	}
	if !buildContext.CgoEnabled {
		have["runtime/cgo"] = true // ignore during walk
	}
	var pkgs []string

	// Commands
	goroot := build.Path[0].Path
	cmd := filepath.Join(goroot, "src/cmd") + string(filepath.Separator)
	filepath.Walk(cmd, func(path string, fi os.FileInfo, err error) error {
		if err != nil || !fi.IsDir() || path == cmd {
			return nil
		}
		name := path[len(cmd):]
		// Commands are all in cmd/, not in subdirectories.
		if strings.Contains(name, string(filepath.Separator)) {
			return filepath.SkipDir
		}

		_, err = build.ScanDir(path)
		if err != nil {
			return nil
		}

		// We use, e.g., cmd/gofmt as the pseudo import path for gofmt.
		name = "cmd/" + name
		if !have[name] {
			have[name] = true
			if match(name) {
				pkgs = append(pkgs, name)
			}
		}
		return nil
	})

	for _, t := range build.Path {
		if pattern == "std" && !t.Goroot {
			continue
		}
		src := t.SrcDir() + string(filepath.Separator)
		filepath.Walk(src, func(path string, fi os.FileInfo, err error) error {
			if err != nil || !fi.IsDir() || path == src {
				return nil
			}

			// Avoid .foo, _foo, and testdata directory trees.
			_, elem := filepath.Split(path)
			if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
				return filepath.SkipDir
			}

			name := filepath.ToSlash(path[len(src):])
			if pattern == "std" && strings.Contains(name, ".") {
				return filepath.SkipDir
			}
			if have[name] {
				return nil
			}
			have[name] = true

			_, err = build.ScanDir(path)
			if err != nil && strings.Contains(err.Error(), "no Go source files") {
				return nil
			}

			if match(name) {
				pkgs = append(pkgs, name)
			}

			// Avoid go/build test data.
			// TODO: Move it into a testdata directory.
			if path == filepath.Join(build.Path[0].SrcDir(), "go/build") {
				return filepath.SkipDir
			}

			return nil
		})
	}

	if len(pkgs) == 0 {
		fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
	}
	return pkgs
}

// allPackagesInFS is like allPackages but is passed a pattern
// beginning ./ or ../, meaning it should scan the tree rooted
// at the given directory.  There are ... in the pattern too.
func allPackagesInFS(pattern string) []string {
	// Find directory to begin the scan.
	// Could be smarter but this one optimization
	// is enough for now, since ... is usually at the
	// end of a path.
	i := strings.Index(pattern, "...")
	dir, _ := path.Split(pattern[:i])

	// pattern begins with ./ or ../.
	// path.Clean will discard the ./ but not the ../.
	// We need to preserve the ./ for pattern matching
	// and in the returned import paths.
	prefix := ""
	if strings.HasPrefix(pattern, "./") {
		prefix = "./"
	}
	match := matchPattern(pattern)

	var pkgs []string
	filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
		if err != nil || !fi.IsDir() || path == dir {
			return nil
		}

		// Avoid .foo, _foo, and testdata directory trees.
		_, elem := filepath.Split(path)
		if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
			return filepath.SkipDir
		}

		name := prefix + filepath.ToSlash(path)
		if !match(name) {
			return nil
		}
		if _, err = build.ScanDir(path); err != nil {
			return nil
		}
		pkgs = append(pkgs, name)
		return nil
	})

	if len(pkgs) == 0 {
		fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
	}
	return pkgs
}

// stringList's arguments should be a sequence of string or []string values.
// stringList flattens them into a single []string.
func stringList(args ...interface{}) []string {
	var x []string
	for _, arg := range args {
		switch arg := arg.(type) {
		case []string:
			x = append(x, arg...)
		case string:
			x = append(x, arg)
		default:
			panic("stringList: invalid argument")
		}
	}
	return x
}

// isLocalPath returns true if arg is an import path denoting
// a local file system directory.  That is, it returns true if the
// path begins with ./ or ../ .
func isLocalPath(arg string) bool {
	return arg == "." || arg == ".." || strings.HasPrefix(arg, "./") || strings.HasPrefix(arg, "../")
}
<commit_msg>cmd/go: fix 'go help <command>'<commit_after>// Copyright 2011 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bytes"
	"flag"
	"fmt"
	"go/build"
	"io"
	"log"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"strings"
	"sync"
	"text/template"
	"unicode"
	"unicode/utf8"
)

// A Command is an implementation of a go command
// like go build or go fix.
type Command struct {
	// Run runs the command.
	// The args are the arguments after the command name.
	Run func(cmd *Command, args []string)

	// UsageLine is the one-line usage message.
	// The first word in the line is taken to be the command name.
	UsageLine string

	// Short is the short description shown in the 'go help' output.
	Short string

	// Long is the long message shown in the 'go help <this-command>' output.
	Long string

	// Flag is a set of flags specific to this command.
	Flag flag.FlagSet

	// CustomFlags indicates that the command will do its own
	// flag parsing.
	CustomFlags bool
}

// Name returns the command's name: the first word in the usage line.
func (c *Command) Name() string {
	name := c.UsageLine
	i := strings.Index(name, " ")
	if i >= 0 {
		name = name[:i]
	}
	return name
}

func (c *Command) Usage() {
	fmt.Fprintf(os.Stderr, "usage: %s\n\n", c.UsageLine)
	fmt.Fprintf(os.Stderr, "%s\n", strings.TrimSpace(c.Long))
	os.Exit(2)
}

// Runnable reports whether the command can be run; otherwise
// it is a documentation pseudo-command such as importpath.
func (c *Command) Runnable() bool {
	return c.Run != nil
}

// Commands lists the available commands and help topics.
// The order here is the order in which they are printed by 'go help'.
var commands = []*Command{
	cmdBuild,
	cmdClean,
	cmdDoc,
	cmdFix,
	cmdFmt,
	cmdGet,
	cmdInstall,
	cmdList,
	cmdRun,
	cmdTest,
	cmdTool,
	cmdVersion,
	cmdVet,

	helpGopath,
	helpImportpath,
	helpRemote,
	helpTestflag,
	helpTestfunc,
}

var exitStatus = 0
var exitMu sync.Mutex

func setExitStatus(n int) {
	exitMu.Lock()
	if exitStatus < n {
		exitStatus = n
	}
	exitMu.Unlock()
}

func main() {
	flag.Usage = usage
	flag.Parse()
	log.SetFlags(0)

	args := flag.Args()
	if len(args) < 1 {
		usage()
	}

	if args[0] == "help" {
		help(args[1:])
		return
	}

	for _, cmd := range commands {
		if cmd.Name() == args[0] && cmd.Run != nil {
			cmd.Flag.Usage = func() { cmd.Usage() }
			if cmd.CustomFlags {
				args = args[1:]
			} else {
				cmd.Flag.Parse(args[1:])
				args = cmd.Flag.Args()
			}
			cmd.Run(cmd, args)
			exit()
			return
		}
	}

	fmt.Fprintf(os.Stderr, "Unknown command %#q\n\n", args[0])
	usage()
}

var usageTemplate = `Go is a tool for managing Go source code.

Usage: go command [arguments]

The commands are:
{{range .}}{{if .Runnable}}
    {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}

Use "go help [command]" for more information about a command.

Additional help topics:
{{range .}}{{if not .Runnable}}
    {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}

Use "go help [topic]" for more information about that topic.

`

var helpTemplate = `{{if .Runnable}}usage: go {{.UsageLine}}

{{end}}{{.Long | trim}}
`

var documentationTemplate = `// Copyright 2011 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
{{range .}}{{if .Short}}{{.Short | capitalize}}

{{end}}{{if .Runnable}}Usage:

	go {{.UsageLine}}

{{end}}{{.Long | trim}}

{{end}}*/
package documentation

// NOTE: cmdDoc is in fmt.go.
`

// tmpl executes the given template text on data, writing the result to w.
func tmpl(w io.Writer, text string, data interface{}) {
	t := template.New("top")
	t.Funcs(template.FuncMap{"trim": strings.TrimSpace, "capitalize": capitalize})
	template.Must(t.Parse(text))
	if err := t.Execute(w, data); err != nil {
		panic(err)
	}
}

func capitalize(s string) string {
	if s == "" {
		return s
	}
	r, n := utf8.DecodeRuneInString(s)
	return string(unicode.ToTitle(r)) + s[n:]
}

func printUsage(w io.Writer) {
	tmpl(w, usageTemplate, commands)
}

func usage() {
	printUsage(os.Stderr)
	os.Exit(2)
}

// help implements the 'help' command.
func help(args []string) {
	if len(args) == 0 {
		printUsage(os.Stdout)
		// not exit 2: succeeded at 'go help'.
		return
	}
	if len(args) != 1 {
		fmt.Fprintf(os.Stderr, "usage: go help command\n\nToo many arguments given.\n")
		os.Exit(2) // failed at 'go help'
	}

	arg := args[0]

	// 'go help documentation' generates doc.go.
	if arg == "documentation" {
		buf := new(bytes.Buffer)
		printUsage(buf)
		usage := &Command{Long: buf.String()}
		tmpl(os.Stdout, documentationTemplate, append([]*Command{usage}, commands...))
		return
	}

	for _, cmd := range commands {
		if cmd.Name() == arg {
			tmpl(os.Stdout, helpTemplate, cmd)
			// not exit 2: succeeded at 'go help cmd'.
			return
		}
	}

	fmt.Fprintf(os.Stderr, "Unknown help topic %#q.  Run 'go help'.\n", arg)
	os.Exit(2) // failed at 'go help cmd'
}

// importPaths returns the import paths to use for the given command line.
func importPaths(args []string) []string {
	if len(args) == 0 {
		return []string{"."}
	}
	var out []string
	for _, a := range args {
		if isLocalPath(a) && strings.Contains(a, "...") {
			out = append(out, allPackagesInFS(a)...)
			continue
		}
		if a == "all" || a == "std" || strings.Contains(a, "...") {
			out = append(out, allPackages(a)...)
			continue
		}
		out = append(out, a)
	}
	return out
}

var atexitFuncs []func()

func atexit(f func()) {
	atexitFuncs = append(atexitFuncs, f)
}

func exit() {
	for _, f := range atexitFuncs {
		f()
	}
	os.Exit(exitStatus)
}

func fatalf(format string, args ...interface{}) {
	errorf(format, args...)
	exit()
}

func errorf(format string, args ...interface{}) {
	log.Printf(format, args...)
	setExitStatus(1)
}

var logf = log.Printf

func exitIfErrors() {
	if exitStatus != 0 {
		exit()
	}
}

func run(cmdargs ...interface{}) {
	cmdline := stringList(cmdargs...)
	cmd := exec.Command(cmdline[0], cmdline[1:]...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		errorf("%v", err)
	}
}

func runOut(dir string, cmdargs ...interface{}) []byte {
	cmdline := stringList(cmdargs...)
	cmd := exec.Command(cmdline[0], cmdline[1:]...)
	cmd.Dir = dir
	out, err := cmd.CombinedOutput()
	if err != nil {
		os.Stderr.Write(out)
		errorf("%v", err)
		out = nil
	}
	return out
}

// matchPattern(pattern)(name) reports whether
// name matches pattern.  Pattern is a limited glob
// pattern in which '...' means 'any string' and there
// is no other special syntax.
func matchPattern(pattern string) func(name string) bool {
	re := regexp.QuoteMeta(pattern)
	re = strings.Replace(re, `\.\.\.`, `.*`, -1)
	reg := regexp.MustCompile(`^` + re + `$`)
	return func(name string) bool {
		return reg.MatchString(name)
	}
}

// allPackages returns all the packages that can be found
// under the $GOPATH directories and $GOROOT matching pattern.
// The pattern is either "all" (all packages), "std" (standard packages)
// or a path including "...".
func allPackages(pattern string) []string {
	match := func(string) bool { return true }
	if pattern != "all" && pattern != "std" {
		match = matchPattern(pattern)
	}

	have := map[string]bool{
		"builtin": true, // ignore pseudo-package that exists only for documentation
	}
	if !buildContext.CgoEnabled {
		have["runtime/cgo"] = true // ignore during walk
	}
	var pkgs []string

	// Commands
	goroot := build.Path[0].Path
	cmd := filepath.Join(goroot, "src/cmd") + string(filepath.Separator)
	filepath.Walk(cmd, func(path string, fi os.FileInfo, err error) error {
		if err != nil || !fi.IsDir() || path == cmd {
			return nil
		}
		name := path[len(cmd):]
		// Commands are all in cmd/, not in subdirectories.
		if strings.Contains(name, string(filepath.Separator)) {
			return filepath.SkipDir
		}

		_, err = build.ScanDir(path)
		if err != nil {
			return nil
		}

		// We use, e.g., cmd/gofmt as the pseudo import path for gofmt.
		name = "cmd/" + name
		if !have[name] {
			have[name] = true
			if match(name) {
				pkgs = append(pkgs, name)
			}
		}
		return nil
	})

	for _, t := range build.Path {
		if pattern == "std" && !t.Goroot {
			continue
		}
		src := t.SrcDir() + string(filepath.Separator)
		filepath.Walk(src, func(path string, fi os.FileInfo, err error) error {
			if err != nil || !fi.IsDir() || path == src {
				return nil
			}

			// Avoid .foo, _foo, and testdata directory trees.
			_, elem := filepath.Split(path)
			if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
				return filepath.SkipDir
			}

			name := filepath.ToSlash(path[len(src):])
			if pattern == "std" && strings.Contains(name, ".") {
				return filepath.SkipDir
			}
			if have[name] {
				return nil
			}
			have[name] = true

			_, err = build.ScanDir(path)
			if err != nil && strings.Contains(err.Error(), "no Go source files") {
				return nil
			}

			if match(name) {
				pkgs = append(pkgs, name)
			}

			// Avoid go/build test data.
			// TODO: Move it into a testdata directory.
			if path == filepath.Join(build.Path[0].SrcDir(), "go/build") {
				return filepath.SkipDir
			}

			return nil
		})
	}

	if len(pkgs) == 0 {
		fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
	}
	return pkgs
}

// allPackagesInFS is like allPackages but is passed a pattern
// beginning ./ or ../, meaning it should scan the tree rooted
// at the given directory.  There are ... in the pattern too.
func allPackagesInFS(pattern string) []string {
	// Find directory to begin the scan.
	// Could be smarter but this one optimization
	// is enough for now, since ... is usually at the
	// end of a path.
	i := strings.Index(pattern, "...")
	dir, _ := path.Split(pattern[:i])

	// pattern begins with ./ or ../.
	// path.Clean will discard the ./ but not the ../.
	// We need to preserve the ./ for pattern matching
	// and in the returned import paths.
	prefix := ""
	if strings.HasPrefix(pattern, "./") {
		prefix = "./"
	}
	match := matchPattern(pattern)

	var pkgs []string
	filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
		if err != nil || !fi.IsDir() || path == dir {
			return nil
		}

		// Avoid .foo, _foo, and testdata directory trees.
		_, elem := filepath.Split(path)
		if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
			return filepath.SkipDir
		}

		name := prefix + filepath.ToSlash(path)
		if !match(name) {
			return nil
		}
		if _, err = build.ScanDir(path); err != nil {
			return nil
		}
		pkgs = append(pkgs, name)
		return nil
	})

	if len(pkgs) == 0 {
		fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
	}
	return pkgs
}

// stringList's arguments should be a sequence of string or []string values.
// stringList flattens them into a single []string.
func stringList(args ...interface{}) []string {
	var x []string
	for _, arg := range args {
		switch arg := arg.(type) {
		case []string:
			x = append(x, arg...)
		case string:
			x = append(x, arg)
		default:
			panic("stringList: invalid argument")
		}
	}
	return x
}

// isLocalPath returns true if arg is an import path denoting
// a local file system directory.  That is, it returns true if the
// path begins with ./ or ../ .
func isLocalPath(arg string) bool {
	return arg == "." || arg == ".." || strings.HasPrefix(arg, "./") || strings.HasPrefix(arg, "../")
}
<|endoftext|>


<commit_before><commit_msg>Instead of writing it in place, write to a different name - then rename the file. This helps to make sure that processes are watching the file never see a 0-length file in the middle of an update.<commit_after><|endoftext|>


<commit_before><commit_msg>fix computing column width for escape codes<commit_after><|endoftext|>


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

package httptrace

import (
	"context"
	"crypto/tls"
	"net/http/httptrace"
	"net/textproto"
	"strings"

	"google.golang.org/grpc/codes"

	"go.opentelemetry.io/api/core"
	"go.opentelemetry.io/api/key"
	"go.opentelemetry.io/api/trace"
)

type clientLevel struct {
	trace.Span
	ident string
}

var (
	HTTPStatus     = key.New("http.status")
	HTTPHeaderMIME = key.New("http.mime")
	HTTPRemoteAddr = key.New("http.remote")
	HTTPLocalAddr  = key.New("http.local")
	MessageKey     = key.New("message",
		key.WithDescription("message text: info, error, etc"),
	)
)

type clientTracer struct {
	context.Context
	httptrace.ClientTrace

	levels []clientLevel
}

func newClientTracer(ctx context.Context) *clientTracer {
	ct := &clientTracer{
		Context: ctx,
	}
	ct.open("http.request")
	return ct
}

func (ct *clientTracer) open(name string, attrs ...core.KeyValue) {
	_, sp := trace.Start(ct.Context, name, trace.WithAttributes(attrs...))
	ct.levels = append(ct.levels, clientLevel{
		Span:  sp,
		ident: name,
	})
}

func (ct *clientTracer) close(name string) {
	if len(ct.levels) == 0 {
		panic("remove me")
	}
	l := len(ct.levels)
	ct.levels[l-1].Finish()
	ct.levels = ct.levels[0 : l-1]
}

func (ct *clientTracer) current() trace.Span {
	return ct.levels[len(ct.levels)-1].Span
}

func (ct *clientTracer) currentName() string {
	if len(ct.levels) == 0 {
		return ""
	}
	return ct.levels[len(ct.levels)-1].ident
}

func (ct *clientTracer) getConn(host string) {
	ct.open("http.getconn", HostKey.String(host))
}

func (ct *clientTracer) gotConn(info httptrace.GotConnInfo) {
	ct.current().SetAttribute(HTTPRemoteAddr.String(info.Conn.RemoteAddr().String()))
	ct.current().SetAttribute(HTTPLocalAddr.String(info.Conn.LocalAddr().String()))

	ct.close("http.getconn")
}

func (ct *clientTracer) putIdleConn(err error) {
	if err != nil {
		ct.current().SetAttribute(MessageKey.String(err.Error()))
		ct.current().SetStatus(codes.Unknown)
	}
	ct.close("http.receive")
}

func (ct *clientTracer) gotFirstResponseByte() {
	ct.open("http.receive")
}

func (ct *clientTracer) dnsStart(httptrace.DNSStartInfo) {
	ct.open("http.dns")
}

func (ct *clientTracer) dnsDone(httptrace.DNSDoneInfo) {
	ct.close("http.dns")
}

func (ct *clientTracer) connectStart(network, addr string) {
	ct.open("http.connect")
}

func (ct *clientTracer) connectDone(network, addr string, err error) {
	ct.close("http.connect")
}

func (ct *clientTracer) tlsHandshakeStart() {
	ct.open("http.tls")
}

func (ct *clientTracer) tlsHandshakeDone(tls.ConnectionState, error) {
	ct.close("http.tls")
}

func (ct *clientTracer) wroteHeaderField(k string, v []string) {
	if ct.currentName() != "http.headers" {
		ct.open("http.headers")
	}
	ct.levels[0].SetAttribute(key.New("http." + strings.ToLower(k)).String(sa2s(v)))
}

func (ct *clientTracer) wroteHeaders() {
	ct.open("http.send")
}

func (ct *clientTracer) wroteRequest(info httptrace.WroteRequestInfo) {
	if info.Err != nil {
		ct.levels[0].SetAttribute(MessageKey.String(info.Err.Error()))
		ct.levels[0].SetStatus(codes.Unknown)
	}
	ct.close("http.send")
}

func (ct *clientTracer) got100Continue() {
	ct.current().AddEvent(ct.Context, "GOT 100 - Continue")
}

func (ct *clientTracer) wait100Continue() {
	ct.current().AddEvent(ct.Context, "GOT 100 - Wait")
}

func (ct *clientTracer) got1xxResponse(code int, header textproto.MIMEHeader) error {
	ct.current().AddEvent(ct.Context, "GOT 1xx",
		HTTPStatus.Int(code),
		HTTPHeaderMIME.String(sm2s(header)),
	)
	return nil
}

func sa2s(value []string) string {
	if len(value) == 1 {
		return value[0]
	} else if len(value) == 0 {
		return "undefined"
	}
	return strings.Join(value, ",")
}

func sm2s(value map[string][]string) string {
	var buf strings.Builder
	for k, v := range value {
		if buf.Len() != 0 {
			buf.WriteString(",")
		}
		buf.WriteString(k)
		buf.WriteString("=")
		buf.WriteString(sa2s(v))
	}
	return buf.String()
}
<commit_msg>httptrace calls arrive async/threaded. (#113)<commit_after>// Copyright 2019, OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package httptrace

import (
	"context"
	"crypto/tls"
	"fmt"
	"net/http/httptrace"
	"net/textproto"
	"strings"
	"sync"

	"google.golang.org/grpc/codes"

	"go.opentelemetry.io/api/core"
	"go.opentelemetry.io/api/key"
	"go.opentelemetry.io/api/trace"
)

var (
	HTTPStatus     = key.New("http.status")
	HTTPHeaderMIME = key.New("http.mime")
	HTTPRemoteAddr = key.New("http.remote")
	HTTPLocalAddr  = key.New("http.local")
	MessageKey     = key.New("message",
		key.WithDescription("message text: info, error, etc"),
	)
)

type clientTracer struct {
	context.Context
	httptrace.ClientTrace

	levels map[string]trace.Span
	root   trace.Span
	mtx    sync.Mutex
}

func newClientTracer(ctx context.Context) *clientTracer {
	ct := &clientTracer{
		Context: ctx,
		levels:  make(map[string]trace.Span),
	}
	ct.open("http.request")
	return ct
}

func (ct *clientTracer) open(name string, attrs ...core.KeyValue) {
	_, sp := trace.Start(ct.Context, name, trace.WithAttributes(attrs...))
	ct.mtx.Lock()
	defer ct.mtx.Unlock()
	if ct.root == nil {
		ct.root = sp
	}
	ct.levels[name] = sp
}

func (ct *clientTracer) close(name string) {
	ct.mtx.Lock()
	defer ct.mtx.Unlock()
	if s, ok := ct.levels[name]; ok {
		s.Finish()
		delete(ct.levels, name)
	} else {
		panic(fmt.Sprintf("failed to find span %s in levels.", name))
	}
}

func (ct *clientTracer) span(name string) trace.Span {
	ct.mtx.Lock()
	defer ct.mtx.Unlock()
	return ct.levels[name]
}

func (ct *clientTracer) getConn(host string) {
	ct.open("http.getconn", HostKey.String(host))
}

func (ct *clientTracer) gotConn(info httptrace.GotConnInfo) {
	ct.span("http.getconn").SetAttribute(HTTPRemoteAddr.String(info.Conn.RemoteAddr().String()))
	ct.span("http.getconn").SetAttribute(HTTPLocalAddr.String(info.Conn.LocalAddr().String()))

	ct.close("http.getconn")
}

func (ct *clientTracer) putIdleConn(err error) {
	if err != nil {
		ct.span("http.receive").SetAttribute(MessageKey.String(err.Error()))
		ct.span("http.receive").SetStatus(codes.Unknown)
	}
	ct.close("http.receive")
}

func (ct *clientTracer) gotFirstResponseByte() {
	ct.open("http.receive")
}

func (ct *clientTracer) dnsStart(httptrace.DNSStartInfo) {
	ct.open("http.dns")
}

func (ct *clientTracer) dnsDone(httptrace.DNSDoneInfo) {
	ct.close("http.dns")
}

func (ct *clientTracer) connectStart(network, addr string) {
	ct.open("http.connect")
}

func (ct *clientTracer) connectDone(network, addr string, err error) {
	ct.close("http.connect")
}

func (ct *clientTracer) tlsHandshakeStart() {
	ct.open("http.tls")
}

func (ct *clientTracer) tlsHandshakeDone(tls.ConnectionState, error) {
	ct.close("http.tls")
}

func (ct *clientTracer) wroteHeaderField(k string, v []string) {
	if ct.span("http.headers") == nil {
		ct.open("http.headers")
	}
	ct.root.SetAttribute(key.New("http." + strings.ToLower(k)).String(sa2s(v)))
}

func (ct *clientTracer) wroteHeaders() {
	ct.open("http.send")
}

func (ct *clientTracer) wroteRequest(info httptrace.WroteRequestInfo) {
	if info.Err != nil {
		ct.root.SetAttribute(MessageKey.String(info.Err.Error()))
		ct.root.SetStatus(codes.Unknown)
	}
	ct.close("http.send")
}

func (ct *clientTracer) got100Continue() {
	ct.span("http.receive").AddEvent(ct.Context, "GOT 100 - Continue")
}

func (ct *clientTracer) wait100Continue() {
	ct.span("http.receive").AddEvent(ct.Context, "GOT 100 - Wait")
}

func (ct *clientTracer) got1xxResponse(code int, header textproto.MIMEHeader) error {
	ct.span("http.receive").AddEvent(ct.Context, "GOT 1xx",
		HTTPStatus.Int(code),
		HTTPHeaderMIME.String(sm2s(header)),
	)
	return nil
}

func sa2s(value []string) string {
	if len(value) == 1 {
		return value[0]
	} else if len(value) == 0 {
		return "undefined"
	}
	return strings.Join(value, ",")
}

func sm2s(value map[string][]string) string {
	var buf strings.Builder
	for k, v := range value {
		if buf.Len() != 0 {
			buf.WriteString(",")
		}
		buf.WriteString(k)
		buf.WriteString("=")
		buf.WriteString(sa2s(v))
	}
	return buf.String()
}
<|endoftext|>


<commit_before>package main

import (
	"example-apps/proxy/handlers"
	"fmt"
	"log"
	"net/http"
	"os"
	"strconv"
)

func launchHandler(port int, downloadHandler, digHandler, pingHandler, proxyHandler, statsHandler, uploadHandler, echoSourceIPHandler http.Handler) {
	mux := http.NewServeMux()
	mux.Handle("/download/", downloadHandler)
	mux.Handle("/dig/", digHandler)
	mux.Handle("/ping/", pingHandler)
	mux.Handle("/proxy/", proxyHandler)
	mux.Handle("/stats", statsHandler)
	mux.Handle("/upload", uploadHandler)
	mux.Handle("/echosourceip", echoSourceIPHandler)
	mux.Handle("/", &handlers.InfoHandler{
		Port: port,
	})
	http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", port), mux)
}

func main() {
	systemPortString := os.Getenv("PORT")
	systemPort, err := strconv.Atoi(systemPortString)
	if err != nil {
		log.Fatal("invalid required env var PORT")
	}

	stats := &handlers.Stats{
		Latency: []float64{},
	}
	downloadHandler := &handlers.DownloadHandler{}
	pingHandler := &handlers.PingHandler{}
	digHandler := &handlers.DigHandler{}
	proxyHandler := &handlers.ProxyHandler{
		Stats: stats,
	}
	statsHandler := &handlers.StatsHandler{
		Stats: stats,
	}
	uploadHandler := &handlers.UploadHandler{}

	echoSourceIPHandler := &handlers.EchoSourceIPHandler{}

	launchHandler(systemPort, downloadHandler, digHandler, pingHandler, proxyHandler, statsHandler, uploadHandler, echoSourceIPHandler)
}
<commit_msg>Add timed_dig endpoint to proxy app<commit_after>package main

import (
	"example-apps/proxy/handlers"
	"fmt"
	"log"
	"net/http"
	"os"
	"strconv"
)

func launchHandler(port int, downloadHandler, digHandler, timedDigHandler, pingHandler, proxyHandler, statsHandler, uploadHandler, echoSourceIPHandler http.Handler) {
	mux := http.NewServeMux()
	mux.Handle("/download/", downloadHandler)
	mux.Handle("/dig/", digHandler)
	mux.Handle("/timed_dig/", timedDigHandler)
	mux.Handle("/ping/", pingHandler)
	mux.Handle("/proxy/", proxyHandler)
	mux.Handle("/stats", statsHandler)
	mux.Handle("/upload", uploadHandler)
	mux.Handle("/echosourceip", echoSourceIPHandler)
	mux.Handle("/", &handlers.InfoHandler{
		Port: port,
	})
	http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", port), mux)
}

func main() {
	systemPortString := os.Getenv("PORT")
	systemPort, err := strconv.Atoi(systemPortString)
	if err != nil {
		log.Fatal("invalid required env var PORT")
	}

	stats := &handlers.Stats{
		Latency: []float64{},
	}
	downloadHandler := &handlers.DownloadHandler{}
	pingHandler := &handlers.PingHandler{}
	digHandler := &handlers.DigHandler{}
	timedDigHandler := &handlers.TimedDigHandler{}
	proxyHandler := &handlers.ProxyHandler{
		Stats: stats,
	}
	statsHandler := &handlers.StatsHandler{
		Stats: stats,
	}
	uploadHandler := &handlers.UploadHandler{}

	echoSourceIPHandler := &handlers.EchoSourceIPHandler{}

	launchHandler(systemPort, downloadHandler, digHandler, timedDigHandler, pingHandler, proxyHandler, statsHandler, uploadHandler, echoSourceIPHandler)
}
<|endoftext|>


<commit_before><commit_msg>Messaging pipeline<commit_after><|endoftext|>


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

package main

import (
	"bufio"
	"fmt"
	"os/exec"
	"strings"
	"sync"
	"syscall"

	"github.com/containernetworking/cni/pkg/invoke"
	"github.com/containernetworking/cni/pkg/skel"
	"github.com/containernetworking/plugins/pkg/ns"
	"github.com/containernetworking/plugins/pkg/testutils"

	"github.com/godbus/dbus"

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

const (
	confTmpl = `{
  "cniVersion": "0.3.1",
  "name": "firewalld-test",
  "type": "firewall",
  "backend": "firewalld",
  "zone": "trusted",
  "prevResult": {
    "cniVersion": "0.3.0",
    "interfaces": [
      {"name": "%s", "sandbox": "%s"}
    ],
    "ips": [
      {
        "version": "4",
        "address": "10.0.0.2/24",
        "gateway": "10.0.0.1",
        "interface": 0
      }
    ],
    "routes": []
  }
}`
	ifname = "eth0"
)

type fakeFirewalld struct {
	zone   string
	source string
}

func (f *fakeFirewalld) clear() {
	f.zone = ""
	f.source = ""
}

func (f *fakeFirewalld) AddSource(zone, source string) (string, *dbus.Error) {
	f.zone = zone
	f.source = source
	return "", nil
}

func (f *fakeFirewalld) RemoveSource(zone, source string) (string, *dbus.Error) {
	f.zone = zone
	f.source = source
	return "", nil
}

func spawnSessionDbus(wg *sync.WaitGroup) (string, *exec.Cmd) {
	// Start a private D-Bus session bus
	path, err := invoke.FindInPath("dbus-daemon", []string{
		"/bin", "/sbin", "/usr/bin", "/usr/sbin",
	})
	Expect(err).NotTo(HaveOccurred())
	cmd := exec.Command(path, "--session", "--print-address", "--nofork", "--nopidfile")
	stdout, err := cmd.StdoutPipe()
	Expect(err).NotTo(HaveOccurred())
	err = cmd.Start()
	Expect(err).NotTo(HaveOccurred())

	// Wait for dbus-daemon to print the bus address
	bytes, err := bufio.NewReader(stdout).ReadString('\n')
	Expect(err).NotTo(HaveOccurred())
	busAddr := strings.TrimSpace(string(bytes))
	Expect(strings.HasPrefix(busAddr, "unix:abstract")).To(BeTrue())

	var startWg sync.WaitGroup
	wg.Add(1)
	startWg.Add(1)
	go func() {
		defer GinkgoRecover()

		startWg.Done()
		err = cmd.Wait()
		Expect(err).NotTo(HaveOccurred())
		wg.Done()
	}()

	startWg.Wait()
	return busAddr, cmd
}

var _ = Describe("firewalld test", func() {
	var (
		targetNs ns.NetNS
		cmd      *exec.Cmd
		conn     *dbus.Conn
		wg       sync.WaitGroup
		fwd      *fakeFirewalld
		busAddr  string
	)

	BeforeEach(func() {
		var err error
		targetNs, err = testutils.NewNS()
		Expect(err).NotTo(HaveOccurred())

		// Start a private D-Bus session bus
		busAddr, cmd = spawnSessionDbus(&wg)
		conn, err = dbus.Dial(busAddr)
		Expect(err).NotTo(HaveOccurred())
		err = conn.Auth(nil)
		Expect(err).NotTo(HaveOccurred())
		err = conn.Hello()
		Expect(err).NotTo(HaveOccurred())

		// Start our fake firewalld
		reply, err := conn.RequestName(firewalldName, dbus.NameFlagDoNotQueue)
		Expect(err).NotTo(HaveOccurred())
		Expect(reply).To(Equal(dbus.RequestNameReplyPrimaryOwner))

		fwd = &fakeFirewalld{}
		// Because firewalld D-Bus methods start with lower-case, and
		// because in Go lower-case methods are private, we need to remap
		// Go public methods to the D-Bus name
		methods := map[string]string{
			"AddSource":    firewalldAddSourceMethod,
			"RemoveSource": firewalldRemoveSourceMethod,
		}
		conn.ExportWithMap(fwd, methods, firewalldPath, firewalldZoneInterface)

		// Make sure the plugin uses our private session bus
		testConn = conn
	})

	AfterEach(func() {
		_, err := conn.ReleaseName(firewalldName)
		Expect(err).NotTo(HaveOccurred())

		err = cmd.Process.Signal(syscall.SIGTERM)
		Expect(err).NotTo(HaveOccurred())

		wg.Wait()
	})

	It("works with a 0.3.1 config", func() {
		Expect(isFirewalldRunning()).To(BeTrue())

		conf := fmt.Sprintf(confTmpl, ifname, targetNs.Path())
		args := &skel.CmdArgs{
			ContainerID: "dummy",
			Netns:       targetNs.Path(),
			IfName:      ifname,
			StdinData:   []byte(conf),
		}
		_, _, err := testutils.CmdAddWithResult(targetNs.Path(), ifname, []byte(conf), func() error {
			return cmdAdd(args)
		})
		Expect(err).NotTo(HaveOccurred())
		Expect(fwd.zone).To(Equal("trusted"))
		Expect(fwd.source).To(Equal("10.0.0.2/32"))
		fwd.clear()

		err = testutils.CmdDelWithResult(targetNs.Path(), ifname, func() error {
			return cmdDel(args)
		})
		Expect(err).NotTo(HaveOccurred())
		Expect(fwd.zone).To(Equal("trusted"))
		Expect(fwd.source).To(Equal("10.0.0.2/32"))
	})
})
<commit_msg>firewall: add a couple more testcases<commit_after>// Copyright 2018 CNI authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"bufio"
	"fmt"
	"os/exec"
	"strings"
	"sync"
	"syscall"

	"github.com/containernetworking/cni/pkg/invoke"
	"github.com/containernetworking/cni/pkg/skel"
	"github.com/containernetworking/cni/pkg/types/current"
	"github.com/containernetworking/plugins/pkg/ns"
	"github.com/containernetworking/plugins/pkg/testutils"

	"github.com/godbus/dbus"

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

const (
	confTmpl = `{
  "cniVersion": "0.3.1",
  "name": "firewalld-test",
  "type": "firewall",
  "backend": "firewalld",
  "zone": "trusted",
  "prevResult": {
    "cniVersion": "0.3.0",
    "interfaces": [
      {"name": "%s", "sandbox": "%s"}
    ],
    "ips": [
      {
        "version": "4",
        "address": "10.0.0.2/24",
        "gateway": "10.0.0.1",
        "interface": 0
      }
    ]
  }
}`
	ifname = "eth0"
)

type fakeFirewalld struct {
	zone   string
	source string
}

func (f *fakeFirewalld) clear() {
	f.zone = ""
	f.source = ""
}

func (f *fakeFirewalld) AddSource(zone, source string) (string, *dbus.Error) {
	f.zone = zone
	f.source = source
	return "", nil
}

func (f *fakeFirewalld) RemoveSource(zone, source string) (string, *dbus.Error) {
	f.zone = zone
	f.source = source
	return "", nil
}

func spawnSessionDbus(wg *sync.WaitGroup) (string, *exec.Cmd) {
	// Start a private D-Bus session bus
	path, err := invoke.FindInPath("dbus-daemon", []string{
		"/bin", "/sbin", "/usr/bin", "/usr/sbin",
	})
	Expect(err).NotTo(HaveOccurred())
	cmd := exec.Command(path, "--session", "--print-address", "--nofork", "--nopidfile")
	stdout, err := cmd.StdoutPipe()
	Expect(err).NotTo(HaveOccurred())
	err = cmd.Start()
	Expect(err).NotTo(HaveOccurred())

	// Wait for dbus-daemon to print the bus address
	bytes, err := bufio.NewReader(stdout).ReadString('\n')
	Expect(err).NotTo(HaveOccurred())
	busAddr := strings.TrimSpace(string(bytes))
	Expect(strings.HasPrefix(busAddr, "unix:abstract")).To(BeTrue())

	var startWg sync.WaitGroup
	wg.Add(1)
	startWg.Add(1)
	go func() {
		defer GinkgoRecover()

		startWg.Done()
		err = cmd.Wait()
		Expect(err).NotTo(HaveOccurred())
		wg.Done()
	}()

	startWg.Wait()
	return busAddr, cmd
}

var _ = Describe("firewalld test", func() {
	var (
		targetNs ns.NetNS
		cmd      *exec.Cmd
		conn     *dbus.Conn
		wg       sync.WaitGroup
		fwd      *fakeFirewalld
		busAddr  string
	)

	BeforeEach(func() {
		var err error
		targetNs, err = testutils.NewNS()
		Expect(err).NotTo(HaveOccurred())

		// Start a private D-Bus session bus
		busAddr, cmd = spawnSessionDbus(&wg)
		conn, err = dbus.Dial(busAddr)
		Expect(err).NotTo(HaveOccurred())
		err = conn.Auth(nil)
		Expect(err).NotTo(HaveOccurred())
		err = conn.Hello()
		Expect(err).NotTo(HaveOccurred())

		// Start our fake firewalld
		reply, err := conn.RequestName(firewalldName, dbus.NameFlagDoNotQueue)
		Expect(err).NotTo(HaveOccurred())
		Expect(reply).To(Equal(dbus.RequestNameReplyPrimaryOwner))

		fwd = &fakeFirewalld{}
		// Because firewalld D-Bus methods start with lower-case, and
		// because in Go lower-case methods are private, we need to remap
		// Go public methods to the D-Bus name
		methods := map[string]string{
			"AddSource":    firewalldAddSourceMethod,
			"RemoveSource": firewalldRemoveSourceMethod,
		}
		conn.ExportWithMap(fwd, methods, firewalldPath, firewalldZoneInterface)

		// Make sure the plugin uses our private session bus
		testConn = conn
	})

	AfterEach(func() {
		_, err := conn.ReleaseName(firewalldName)
		Expect(err).NotTo(HaveOccurred())

		err = cmd.Process.Signal(syscall.SIGTERM)
		Expect(err).NotTo(HaveOccurred())

		wg.Wait()
	})

	It("works with a 0.3.1 config", func() {
		Expect(isFirewalldRunning()).To(BeTrue())

		conf := fmt.Sprintf(confTmpl, ifname, targetNs.Path())
		args := &skel.CmdArgs{
			ContainerID: "dummy",
			Netns:       targetNs.Path(),
			IfName:      ifname,
			StdinData:   []byte(conf),
		}
		_, _, err := testutils.CmdAddWithResult(targetNs.Path(), ifname, []byte(conf), func() error {
			return cmdAdd(args)
		})
		Expect(err).NotTo(HaveOccurred())
		Expect(fwd.zone).To(Equal("trusted"))
		Expect(fwd.source).To(Equal("10.0.0.2/32"))
		fwd.clear()

		err = testutils.CmdDelWithResult(targetNs.Path(), ifname, func() error {
			return cmdDel(args)
		})
		Expect(err).NotTo(HaveOccurred())
		Expect(fwd.zone).To(Equal("trusted"))
		Expect(fwd.source).To(Equal("10.0.0.2/32"))
	})

	It("defaults to the firewalld backend", func() {
		conf := `{
		  "cniVersion": "0.3.1",
		  "name": "firewalld-test",
		  "type": "firewall",
		  "zone": "trusted",
		  "prevResult": {
		    "cniVersion": "0.3.0",
		    "interfaces": [
		      {"name": "eth0", "sandbox": "/foobar"}
		    ],
		    "ips": [
		      {
			"version": "4",
			"address": "10.0.0.2/24",
			"gateway": "10.0.0.1",
			"interface": 0
		      }
		    ]
		  }
		}`

		Expect(isFirewalldRunning()).To(BeTrue())

		args := &skel.CmdArgs{
			ContainerID: "dummy",
			Netns:       targetNs.Path(),
			IfName:      ifname,
			StdinData:   []byte(conf),
		}
		_, _, err := testutils.CmdAddWithResult(targetNs.Path(), ifname, []byte(conf), func() error {
			return cmdAdd(args)
		})
		Expect(err).NotTo(HaveOccurred())
		Expect(fwd.zone).To(Equal("trusted"))
		Expect(fwd.source).To(Equal("10.0.0.2/32"))
	})

	It("passes through the prevResult", func() {
		conf := `{
		  "cniVersion": "0.3.1",
		  "name": "firewalld-test",
		  "type": "firewall",
		  "zone": "trusted",
		  "prevResult": {
		    "cniVersion": "0.3.0",
		    "interfaces": [
		      {"name": "eth0", "sandbox": "/foobar"}
		    ],
		    "ips": [
		      {
			"version": "4",
			"address": "10.0.0.2/24",
			"gateway": "10.0.0.1",
			"interface": 0
		      }
		    ]
		  }
		}`

		Expect(isFirewalldRunning()).To(BeTrue())

		args := &skel.CmdArgs{
			ContainerID: "dummy",
			Netns:       targetNs.Path(),
			IfName:      ifname,
			StdinData:   []byte(conf),
		}
		r, _, err := testutils.CmdAddWithResult(targetNs.Path(), ifname, []byte(conf), func() error {
			return cmdAdd(args)
		})
		Expect(err).NotTo(HaveOccurred())

		result, err := current.GetResult(r)
		Expect(err).NotTo(HaveOccurred())

		Expect(len(result.Interfaces)).To(Equal(1))
		Expect(result.Interfaces[0].Name).To(Equal("eth0"))
		Expect(len(result.IPs)).To(Equal(1))
		Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
	})
})
<|endoftext|>


<commit_before>/*
Funkensturm rewrites DNS packets in the broadest sense of the word.
The rewritting can include delayed (re)sending of packets, (re)sending
packets to multiple servers, rewriting the packet contents, for instance
by signing a packet, or the other way around, stripping the signatures.

In its essence this is no different that a recursive nameserver, which also
receives and sends queries. The difference is the huge amount of tweaking
funkensturm offers.

It includes a configuration language which makes setting up funkensturm
real easy. (It may be the case that this configuration language will be Go)

Not sure if this is doable:
- support packet of death (TSIG signed) for stopping funkensturm
- support packet of config (TSIG signed) for configuring funkensturm on the fly

*/
package documentation
<commit_msg>fix doc<commit_after>/*
Funkensturm rewrites DNS packets in the broadest sense of the word.
The rewriting can include delayed (re)sending of packets, (re)sending
packets to multiple servers, rewriting the packet contents, for instance
by signing a packet, or the other way around, stripping the signatures.

In its essence this is no different that a recursive nameserver, which also
receives and sends queries. The difference is the huge amount of tweaking
Funkensturm offers.

The configuration of Funkensturm is done by writing it in Go - a
separate configuration language was deemed to be unpractical and
would limit the possibilities.

*/
package documentation
<|endoftext|>


<commit_before>package main

import (
	"fullerite/config"
	"fullerite/handler"
	"fullerite/metric"

	"fmt"
	"testing"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/stretchr/testify/assert"
)

func TestStartHandlersEmptyConfig(t *testing.T) {
	logrus.SetLevel(logrus.PanicLevel)
	handlers := startHandlers(config.Config{})

	assert.Zero(t, len(handlers), "should not create any Handler")
}

func TestStartHandlerUnknownHandler(t *testing.T) {
	logrus.SetLevel(logrus.PanicLevel)

	c := make(map[string]interface{})
	handler := startHandler("unknown handler", config.Config{}, c)

	assert.Nil(t, handler)
}

func checkEmission(t *testing.T, coll string, h handler.Handler, expected bool) {
	m := metric.Metric{
		Name:       "test",
		Value:      1,
		Dimensions: map[string]string{"collector": coll},
	}
	writeToHandlers([]handler.Handler{h}, m)
	ch, _ := h.CollectorChannels()[coll]
	fmt.Println(h.CollectorChannels(), ch)
	if !expected && ch != nil {
		assert.Fail(t, fmt.Sprintf("Was not expecting a collector channel for %s", coll))
	}
	if expected && ch == nil {
		assert.Fail(t, fmt.Sprintf("Was expecting a collector channel for %s", coll))
	}
}

func TestCanSendMetricsWhiteList(t *testing.T) {
	logrus.SetLevel(logrus.PanicLevel)

	channel := make(chan metric.Metric, 5)
	timeout := time.Duration(5 * time.Second)
	log := logrus.WithFields(logrus.Fields{"app": "fullerite", "pkg": "handler"})
	h := handler.NewTest(channel, 10, 10, timeout, log)
	c := config.Config{
		Collectors: []string{"coll1", "coll2", "coll3"},
	}
	h.SetCollectorWhiteList([]string{"coll1", "coll2"})
	h.SetCollectorBlackList([]string{"coll2"})
	h.InitListeners(c)

	checkEmission(t, "coll1", h, true)
	checkEmission(t, "coll2", h, false)
	checkEmission(t, "coll3", h, false)
}

func TestCanSendMetricsOnlyBlackList(t *testing.T) {
	logrus.SetLevel(logrus.PanicLevel)

	channel := make(chan metric.Metric, 5)
	timeout := time.Duration(5 * time.Second)
	log := logrus.WithFields(logrus.Fields{"app": "fullerite", "pkg": "handler"})
	h := handler.NewTest(channel, 10, 10, timeout, log)
	c := config.Config{
		Collectors: []string{"coll1", "coll2", "coll3"},
	}
	h.SetCollectorBlackList([]string{"coll2"})
	h.InitListeners(c)

	checkEmission(t, "coll1", h, true)
	checkEmission(t, "coll2", h, false)
	checkEmission(t, "coll3", h, true)
}

func TestCanSendMetrics(t *testing.T) {
	logrus.SetLevel(logrus.PanicLevel)

	channel := make(chan metric.Metric, 5)
	timeout := time.Duration(5 * time.Second)
	log := logrus.WithFields(logrus.Fields{"app": "fullerite", "pkg": "handler"})
	h := handler.NewTest(channel, 10, 10, timeout, log)
	c := config.Config{
		Collectors: []string{"coll1", "coll2", "coll3"},
	}
	h.InitListeners(c)

	checkEmission(t, "coll1", h, true)
	checkEmission(t, "coll2", h, true)
	checkEmission(t, "coll3", h, true)
}
<commit_msg>Remove debug text<commit_after>package main

import (
	"fullerite/config"
	"fullerite/handler"
	"fullerite/metric"

	"fmt"
	"testing"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/stretchr/testify/assert"
)

func TestStartHandlersEmptyConfig(t *testing.T) {
	logrus.SetLevel(logrus.PanicLevel)
	handlers := startHandlers(config.Config{})

	assert.Zero(t, len(handlers), "should not create any Handler")
}

func TestStartHandlerUnknownHandler(t *testing.T) {
	logrus.SetLevel(logrus.PanicLevel)

	c := make(map[string]interface{})
	handler := startHandler("unknown handler", config.Config{}, c)

	assert.Nil(t, handler)
}

func checkEmission(t *testing.T, coll string, h handler.Handler, expected bool) {
	m := metric.Metric{
		Name:       "test",
		Value:      1,
		Dimensions: map[string]string{"collector": coll},
	}
	writeToHandlers([]handler.Handler{h}, m)
	ch, _ := h.CollectorChannels()[coll]
	if !expected && ch != nil {
		assert.Fail(t, fmt.Sprintf("Was not expecting a collector channel for %s", coll))
	}
	if expected && ch == nil {
		assert.Fail(t, fmt.Sprintf("Was expecting a collector channel for %s", coll))
	}
}

func TestCanSendMetricsWhiteList(t *testing.T) {
	logrus.SetLevel(logrus.PanicLevel)

	channel := make(chan metric.Metric, 5)
	timeout := time.Duration(5 * time.Second)
	log := logrus.WithFields(logrus.Fields{"app": "fullerite", "pkg": "handler"})
	h := handler.NewTest(channel, 10, 10, timeout, log)
	c := config.Config{
		Collectors: []string{"coll1", "coll2", "coll3"},
	}
	h.SetCollectorWhiteList([]string{"coll1", "coll2"})
	h.SetCollectorBlackList([]string{"coll2"})
	h.InitListeners(c)

	checkEmission(t, "coll1", h, true)
	checkEmission(t, "coll2", h, false)
	checkEmission(t, "coll3", h, false)
}

func TestCanSendMetricsOnlyBlackList(t *testing.T) {
	logrus.SetLevel(logrus.PanicLevel)

	channel := make(chan metric.Metric, 5)
	timeout := time.Duration(5 * time.Second)
	log := logrus.WithFields(logrus.Fields{"app": "fullerite", "pkg": "handler"})
	h := handler.NewTest(channel, 10, 10, timeout, log)
	c := config.Config{
		Collectors: []string{"coll1", "coll2", "coll3"},
	}
	h.SetCollectorBlackList([]string{"coll2"})
	h.InitListeners(c)

	checkEmission(t, "coll1", h, true)
	checkEmission(t, "coll2", h, false)
	checkEmission(t, "coll3", h, true)
}

func TestCanSendMetrics(t *testing.T) {
	logrus.SetLevel(logrus.PanicLevel)

	channel := make(chan metric.Metric, 5)
	timeout := time.Duration(5 * time.Second)
	log := logrus.WithFields(logrus.Fields{"app": "fullerite", "pkg": "handler"})
	h := handler.NewTest(channel, 10, 10, timeout, log)
	c := config.Config{
		Collectors: []string{"coll1", "coll2", "coll3"},
	}
	h.InitListeners(c)

	checkEmission(t, "coll1", h, true)
	checkEmission(t, "coll2", h, true)
	checkEmission(t, "coll3", h, true)
}
<|endoftext|>


<commit_before>// Package main implements a parser for our config structure
// that emits help topics based on its struct tags.
package main

import (
	"encoding/json"
	"fmt"
	"reflect"
	"regexp"
	"strings"

	"core"
)

type output struct {
	Preamble string            `json:"preamble"`
	Topics   map[string]string `json:"topics"`
}

var urlRegex = regexp.MustCompile("https?://[^ ]+[^.]")

// ExampleValue returns an example value for a config field based on its type.
func ExampleValue(f reflect.Value, name string, t reflect.Type, example string) string {
	if t.Kind() == reflect.Slice {
		return ExampleValue(f, name, t.Elem(), example) + fmt.Sprintf("\n\n%s can be repeated", name)
	} else if example != "" {
		return example
	} else if name == "version" {
		return core.PleaseVersion.String() // keep it up to date!
	} else if t.Kind() == reflect.String {
		if f.String() != "" {
			return f.String()
		}
		if t.Name() == "URL" {
			return "https://mydomain.com/somepath"
		}
		return "<str>"
	} else if t.Kind() == reflect.Bool {
		return "true | false | yes | no | on | off"
	} else if t.Name() == "Duration" {
		return "10ms | 20s | 5m"
	} else if t.Kind() == reflect.Int || t.Kind() == reflect.Int64 {
		if f.Int() != 0 {
			return fmt.Sprintf("%d", f.Int())
		}
		return "42"
	} else if t.Name() == "ByteSize" {
		return "5K | 10MB | 20GiB"
	} else if t.Kind() == reflect.Uint64 {
		return fmt.Sprintf("%d", f.Uint())
	} else if t.Name() == "BuildLabel" {
		return "//src/core:core"
	}
	panic(fmt.Sprintf("Unknown type: %s", t.Kind()))
}

func main() {
	o := output{
		Preamble: "${BOLD_BLUE}%s${RESET} is a config setting defined in the .plzconfig file. See `plz help plzconfig` for more information.",
		Topics:   map[string]string{},
	}
	config := core.DefaultConfiguration()
	v := reflect.ValueOf(*config)
	t := v.Type()
	for i := 0; i < t.NumField(); i++ {
		f := v.Field(i)
		sectname := strings.ToLower(t.Field(i).Name)
		subfields := []string{}
		if f.Type().Kind() == reflect.Struct {
			for j := 0; j < f.Type().NumField(); j++ {
				subf := f.Field(j)
				subt := t.Field(i).Type.Field(j)
				if help := subt.Tag.Get("help"); help != "" {
					name := strings.ToLower(subt.Name)
					example := subt.Tag.Get("example")
					preamble := fmt.Sprintf("${BOLD_YELLOW}[%s]${RESET}\n${YELLOW}%s${RESET} = ${GREEN}%s${RESET}\n\n", sectname, name, ExampleValue(subf, name, subt.Type, example))
					help = strings.Replace(help, "\\n", "\n", -1)
					o.Topics[name] = preamble + help
					subfields = append(subfields, "  "+name)
				} else {
					panic(fmt.Sprintf("Missing help struct tag on %s.%s", t.Field(i).Name, subt.Name))
				}
			}
		}
		if help := t.Field(i).Tag.Get("help"); help != "" {
			if len(subfields) > 0 {
				help += "\n\n${YELLOW}This option has the following sub-fields:${RESET}\n${GREEN}" + strings.Join(subfields, "\n") + "${RESET}"
			}
			o.Topics[sectname] = urlRegex.ReplaceAllStringFunc(help, func(s string) string { return "${BLUE}" + s + "${RESET}" })
		}
	}
	b, _ := json.Marshal(o)
	fmt.Println(string(b))
}
<commit_msg>add missing newline<commit_after>// Package main implements a parser for our config structure
// that emits help topics based on its struct tags.
package main

import (
	"encoding/json"
	"fmt"
	"reflect"
	"regexp"
	"strings"

	"core"
)

type output struct {
	Preamble string            `json:"preamble"`
	Topics   map[string]string `json:"topics"`
}

var urlRegex = regexp.MustCompile("https?://[^ ]+[^.]")

// ExampleValue returns an example value for a config field based on its type.
func ExampleValue(f reflect.Value, name string, t reflect.Type, example string) string {
	if t.Kind() == reflect.Slice {
		return ExampleValue(f, name, t.Elem(), example) + fmt.Sprintf("\n\n%s can be repeated", name)
	} else if example != "" {
		return example
	} else if name == "version" {
		return core.PleaseVersion.String() // keep it up to date!
	} else if t.Kind() == reflect.String {
		if f.String() != "" {
			return f.String()
		}
		if t.Name() == "URL" {
			return "https://mydomain.com/somepath"
		}
		return "<str>"
	} else if t.Kind() == reflect.Bool {
		return "true | false | yes | no | on | off"
	} else if t.Name() == "Duration" {
		return "10ms | 20s | 5m"
	} else if t.Kind() == reflect.Int || t.Kind() == reflect.Int64 {
		if f.Int() != 0 {
			return fmt.Sprintf("%d", f.Int())
		}
		return "42"
	} else if t.Name() == "ByteSize" {
		return "5K | 10MB | 20GiB"
	} else if t.Kind() == reflect.Uint64 {
		return fmt.Sprintf("%d", f.Uint())
	} else if t.Name() == "BuildLabel" {
		return "//src/core:core"
	}
	panic(fmt.Sprintf("Unknown type: %s", t.Kind()))
}

func main() {
	o := output{
		Preamble: "${BOLD_BLUE}%s${RESET} is a config setting defined in the .plzconfig file. See `plz help plzconfig` for more information.",
		Topics:   map[string]string{},
	}
	config := core.DefaultConfiguration()
	v := reflect.ValueOf(*config)
	t := v.Type()
	for i := 0; i < t.NumField(); i++ {
		f := v.Field(i)
		sectname := strings.ToLower(t.Field(i).Name)
		subfields := []string{}
		if f.Type().Kind() == reflect.Struct {
			for j := 0; j < f.Type().NumField(); j++ {
				subf := f.Field(j)
				subt := t.Field(i).Type.Field(j)
				if help := subt.Tag.Get("help"); help != "" {
					name := strings.ToLower(subt.Name)
					example := subt.Tag.Get("example")
					preamble := fmt.Sprintf("${BOLD_YELLOW}[%s]${RESET}\n${YELLOW}%s${RESET} = ${GREEN}%s${RESET}\n\n", sectname, name, ExampleValue(subf, name, subt.Type, example))
					help = strings.Replace(help, "\\n", "\n", -1)
					o.Topics[name] = preamble + help
					subfields = append(subfields, "  "+name)
				} else {
					panic(fmt.Sprintf("Missing help struct tag on %s.%s", t.Field(i).Name, subt.Name))
				}
			}
		}
		if help := t.Field(i).Tag.Get("help"); help != "" {
			if len(subfields) > 0 {
				help += "\n\n${YELLOW}This option has the following sub-fields:${RESET}\n${GREEN}" + strings.Join(subfields, "\n") + "${RESET}\n"
			}
			o.Topics[sectname] = urlRegex.ReplaceAllStringFunc(help, func(s string) string { return "${BLUE}" + s + "${RESET}" })
		}
	}
	b, _ := json.Marshal(o)
	fmt.Println(string(b))
}
<|endoftext|>