<commit_before>package sentry

import (
	"fmt"
	"net/http"
	"testing"

	"time"

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

func TestOrganizationService_List(t *testing.T) {
	httpClient, mux, server := testServer()
	defer server.Close()

	mux.HandleFunc("/api/0/organizations/", func(w http.ResponseWriter, r *http.Request) {
		assertMethod(t, "GET", r)
		assertQuery(t, map[string]string{"cursor": "1500300636142:0:1"}, r)
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprint(w, `[
			{
				"name": "The Interstellar Jurisdiction",
				"slug": "the-interstellar-jurisdiction",
				"avatar": {
					"avatarUuid": null,
					"avatarType": "letter_avatar"
				},
				"dateCreated": "2017-07-17T14:10:36.141Z",
				"id": "2",
				"isEarlyAdopter": false
			}
		]`)
	})

	client := NewClient(httpClient, nil, "")
	organizations, _, err := client.Organizations.List(&ListOrganizationParams{
		Cursor: "1500300636142:0:1",
	})
	assert.NoError(t, err)

	expectedDateCreated, _ := time.Parse(time.RFC3339, "2017-07-17T14:10:36.141Z")
	expected := []Organization{
		{
			ID:             "2",
			Slug:           "the-interstellar-jurisdiction",
			Name:           "The Interstellar Jurisdiction",
			DateCreated:    expectedDateCreated,
			IsEarlyAdopter: false,
			Avatar: &OrganizationAvatar{
				UUID: nil,
				Type: "letter_avatar",
			},
		},
	}
	assert.Equal(t, expected, organizations)
}

func TestOrganizationService_Create(t *testing.T) {
	httpClient, mux, server := testServer()
	defer server.Close()

	mux.HandleFunc("/api/0/organizations/", func(w http.ResponseWriter, r *http.Request) {
		assertMethod(t, "POST", r)
		assertPostJSON(t, map[string]interface{}{
			"name": "The Interstellar Jurisdiction",
			"slug": "the-interstellar-jurisdiction",
		}, r)
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprint(w, `{
			"name": "The Interstellar Jurisdiction",
			"slug": "the-interstellar-jurisdiction",
			"id": "2"
		}`)
	})

	client := NewClient(httpClient, nil, "")
	params := &CreateOrganizationParams{
		Name: "The Interstellar Jurisdiction",
		Slug: "the-interstellar-jurisdiction",
	}
	organization, _, err := client.Organizations.Create(params)
	assert.NoError(t, err)
	expected := &Organization{
		ID:   "2",
		Name: "The Interstellar Jurisdiction",
		Slug: "the-interstellar-jurisdiction",
	}
	assert.Equal(t, expected, organization)
}

func TestOrganizationService_Update(t *testing.T) {
	httpClient, mux, server := testServer()
	defer server.Close()

	mux.HandleFunc("/api/0/organizations/badly-misnamed/", func(w http.ResponseWriter, r *http.Request) {
		assertMethod(t, "PUT", r)
		assertPostJSON(t, map[string]interface{}{
			"name": "Impeccably Designated",
			"slug": "impeccably-designated",
		}, r)
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprint(w, `{
			"name": "Impeccably Designated",
			"slug": "impeccably-designated",
			"id": "2"
		}`)
	})

	client := NewClient(httpClient, nil, "")
	params := &UpdateOrganizationParams{
		Name: "Impeccably Designated",
		Slug: "impeccably-designated",
	}
	organization, _, err := client.Organizations.Update("badly-misnamed", params)
	assert.NoError(t, err)
	expected := &Organization{
		ID:   "2",
		Name: "Impeccably Designated",
		Slug: "impeccably-designated",
	}
	assert.Equal(t, expected, organization)
}
<commit_msg>Test delete organization<commit_after>package sentry

import (
	"fmt"
	"net/http"
	"testing"

	"time"

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

func TestOrganizationService_List(t *testing.T) {
	httpClient, mux, server := testServer()
	defer server.Close()

	mux.HandleFunc("/api/0/organizations/", func(w http.ResponseWriter, r *http.Request) {
		assertMethod(t, "GET", r)
		assertQuery(t, map[string]string{"cursor": "1500300636142:0:1"}, r)
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprint(w, `[
			{
				"name": "The Interstellar Jurisdiction",
				"slug": "the-interstellar-jurisdiction",
				"avatar": {
					"avatarUuid": null,
					"avatarType": "letter_avatar"
				},
				"dateCreated": "2017-07-17T14:10:36.141Z",
				"id": "2",
				"isEarlyAdopter": false
			}
		]`)
	})

	client := NewClient(httpClient, nil, "")
	organizations, _, err := client.Organizations.List(&ListOrganizationParams{
		Cursor: "1500300636142:0:1",
	})
	assert.NoError(t, err)

	expectedDateCreated, _ := time.Parse(time.RFC3339, "2017-07-17T14:10:36.141Z")
	expected := []Organization{
		{
			ID:             "2",
			Slug:           "the-interstellar-jurisdiction",
			Name:           "The Interstellar Jurisdiction",
			DateCreated:    expectedDateCreated,
			IsEarlyAdopter: false,
			Avatar: &OrganizationAvatar{
				UUID: nil,
				Type: "letter_avatar",
			},
		},
	}
	assert.Equal(t, expected, organizations)
}

func TestOrganizationService_Create(t *testing.T) {
	httpClient, mux, server := testServer()
	defer server.Close()

	mux.HandleFunc("/api/0/organizations/", func(w http.ResponseWriter, r *http.Request) {
		assertMethod(t, "POST", r)
		assertPostJSON(t, map[string]interface{}{
			"name": "The Interstellar Jurisdiction",
			"slug": "the-interstellar-jurisdiction",
		}, r)
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprint(w, `{
			"name": "The Interstellar Jurisdiction",
			"slug": "the-interstellar-jurisdiction",
			"id": "2"
		}`)
	})

	client := NewClient(httpClient, nil, "")
	params := &CreateOrganizationParams{
		Name: "The Interstellar Jurisdiction",
		Slug: "the-interstellar-jurisdiction",
	}
	organization, _, err := client.Organizations.Create(params)
	assert.NoError(t, err)
	expected := &Organization{
		ID:   "2",
		Name: "The Interstellar Jurisdiction",
		Slug: "the-interstellar-jurisdiction",
	}
	assert.Equal(t, expected, organization)
}

func TestOrganizationService_Update(t *testing.T) {
	httpClient, mux, server := testServer()
	defer server.Close()

	mux.HandleFunc("/api/0/organizations/badly-misnamed/", func(w http.ResponseWriter, r *http.Request) {
		assertMethod(t, "PUT", r)
		assertPostJSON(t, map[string]interface{}{
			"name": "Impeccably Designated",
			"slug": "impeccably-designated",
		}, r)
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprint(w, `{
			"name": "Impeccably Designated",
			"slug": "impeccably-designated",
			"id": "2"
		}`)
	})

	client := NewClient(httpClient, nil, "")
	params := &UpdateOrganizationParams{
		Name: "Impeccably Designated",
		Slug: "impeccably-designated",
	}
	organization, _, err := client.Organizations.Update("badly-misnamed", params)
	assert.NoError(t, err)
	expected := &Organization{
		ID:   "2",
		Name: "Impeccably Designated",
		Slug: "impeccably-designated",
	}
	assert.Equal(t, expected, organization)
}

func TestOrganizationService_Delete(t *testing.T) {
	httpClient, mux, server := testServer()
	defer server.Close()

	mux.HandleFunc("/api/0/organizations/the-interstellar-jurisdiction/", func(w http.ResponseWriter, r *http.Request) {
		assertMethod(t, "DELETE", r)
	})

	client := NewClient(httpClient, nil, "")
	_, err := client.Organizations.Delete("the-interstellar-jurisdiction")
	assert.NoError(t, err)
}
<|endoftext|>


<commit_before>package seqdiagram

import (
	"errors"
	"sort"

	"github.com/lmika/goseq/seqdiagram/graphbox"
)

// Various position offsets
const (
	posObjectLeftX = 1
	posObjectY     = 1
)

var graphboxArrowStemMapping = map[ArrowStem]graphbox.ActivityArrowStem{
	SolidArrowStem:  graphbox.SolidArrowStem,
	DashedArrowStem: graphbox.DashedArrowStem,
	ThickArrowStem:  graphbox.ThickArrowStem,
}

// Load the internal font
func mustLoadFont() *graphbox.TTFFont {
	font, err := loadInternalFont(dejaVuSansFont)
	if err != nil {
		panic(errors.New("Could not load internal font: " + dejaVuSansFont))
	}

	return font
}

// Information about a particular actor
type actorInfo struct {
	// Extra cols needed on the left or right
	ExtraLeftCol  bool
	ExtraRightCol bool

	// Actor column
	Col int
}

type graphicBuilder struct {
	Diagram *Diagram
	Graphic *graphbox.Graphic
	Style   *DiagramStyles

	actorInfos []actorInfo
}

func newGraphicBuilder(d *Diagram, style *DiagramStyles) (*graphicBuilder, error) {
	return &graphicBuilder{d, nil, style, nil}, nil
}

func (gb *graphicBuilder) buildGraphic() *graphbox.Graphic {
	rows, cols := gb.calcRowsAndCols()
	gb.Graphic = graphbox.NewGraphic(rows, cols)

	gb.Graphic.Margin = gb.Style.Margin
	gb.Graphic.ShowGrid = false

	gb.addActors()

	if len(gb.Diagram.Items) == 0 {
		gb.Graphic.Put(2, 0, &graphbox.Spacer{graphbox.Point{0, 64}})
	} else {
		row := 2
		gb.putItemsInSlice(&row, 0, gb.Diagram.Items)
	}

	// Add a title
	if gb.Diagram.Title != "" {
		gb.Graphic.Put(0, 0, graphbox.NewTitle(cols, gb.Diagram.Title, gb.Style.Title))
	}

	return gb.Graphic
}

// Place items in a slice.  This will update the rows pointer
func (gb *graphicBuilder) putItemsInSlice(row *int, depth int, items []SequenceItem) {
	for _, item := range items {
		switch itemDetails := item.(type) {
		case *Action:
			gb.putAction(*row, itemDetails)
		case *Note:
			gb.putNote(*row, itemDetails)
		case *Divider:
			gb.putDivider(*row, itemDetails)
		case *Block:
			gb.putBlock(row, depth, itemDetails)
		}

		*row += 1
	}
}

// Calculate rows in slice
func (gb *graphicBuilder) calcItemsInSlice(items []SequenceItem) int {
	rows := 0
	for _, item := range items {
		switch itemDetails := item.(type) {
		case *Block:
			if itemDetails.Concurrent() {
				maxRows := 0
				for _, seg := range itemDetails.Segments {
					rowsInSeg := gb.calcItemsInSlice(seg.SubItems) + 1
					if rowsInSeg > maxRows {
						maxRows = rowsInSeg
					}
				}
				rows += maxRows
			} else {
				for _, seg := range itemDetails.Segments {
					rows += gb.calcItemsInSlice(seg.SubItems) + 1
				}
			}
			rows += 1
		default:
			rows++
		}
	}
	return rows
}

// Places a note
func (gb *graphicBuilder) putNote(row int, note *Note) {
	if (note.Actor2 == nil) || (note.Actor1 == note.Actor2) {
		gb.putSingleActorNote(row, note.Actor1, note)
	} else {
		var leftActor, rightActor *Actor
		if gb.colOfActor(note.Actor1) < gb.colOfActor(note.Actor2) {
			leftActor, rightActor = note.Actor1, note.Actor2
		} else {
			leftActor, rightActor = note.Actor2, note.Actor1
		}

		switch note.Align {
		case OverNoteAlignment:
			gb.putMultiActorOverNote(row, leftActor, rightActor, note)
		case LeftNoteAlignment:
			gb.putSingleActorNote(row, leftActor, note)
		case RightNoteAlignment:
			gb.putSingleActorNote(row, rightActor, note)
		}
	}
}

// Places a note over a single actor
func (gb *graphicBuilder) putSingleActorNote(row int, actor *Actor, note *Note) {
	var pos graphbox.NoteBoxPos

	if note.Align == LeftNoteAlignment {
		pos = graphbox.LeftNotePos
	} else if note.Align == OverNoteAlignment {
		pos = graphbox.CenterNotePos
	} else if note.Align == RightNoteAlignment {
		pos = graphbox.RightNotePos
	}

	col := gb.colOfActor(actor)
	gb.Graphic.Put(row, col, graphbox.NewNoteBox(note.Message, gb.Style.NoteBox, pos))
}

// Places a note over a multiple actors.  This actually uses the divider graphics object
// with the style adopted from the note style
func (gb *graphicBuilder) putMultiActorOverNote(row int, leftActor *Actor, rightActor *Actor, note *Note) {
	dividerBox := graphbox.DividerStyle{
		Font:        gb.Style.NoteBox.Font,
		FontSize:    gb.Style.NoteBox.FontSize,
		Padding:     gb.Style.NoteBox.Padding,
		Margin:      gb.Style.NoteBox.Margin,
		TextPadding: graphbox.Point{0, 0},
		Shape:       graphbox.DSFramedRect,
		Overlap:     gb.Style.MultiNoteOverlap,
	}

	fromCol := gb.colOfActor(leftActor)
	toCol := gb.colOfActor(rightActor)

	// TODO: This was just to avoid bad styling of notes which reference 'left' and 'right'
	// This needs to be fixed using proper styling, instead of this hack.
	if fromCol == 0 {
		fromCol = 1
	}
	if toCol == gb.Graphic.Cols()-1 {
		toCol = gb.Graphic.Cols() - 2
	}

	gb.Graphic.Put(row, fromCol, graphbox.NewDivider(toCol, note.Message, dividerBox))
}

// Places an action
func (gb *graphicBuilder) putAction(row int, action *Action) {
	fromCol := gb.colOfActor(action.From)
	toCol := gb.colOfActor(action.To)

	style := gb.Style.ActivityLine

	style.ArrowHead = gb.Style.ArrowHeads[action.Arrow.Head] //graphboxArrowHeadMapping[action.Arrow.Head]
	style.ArrowStem = graphboxArrowStemMapping[action.Arrow.Stem]

	gb.Graphic.Put(row, fromCol, graphbox.NewActivityLine(toCol, fromCol == toCol, action.Message, style))
}

// Places a divider
func (gb *graphicBuilder) putDivider(row int, action *Divider) {
	fromCol := 0
	toCol := gb.Graphic.Cols() - 1
	style := gb.Style.Divider[action.Type]

	gb.Graphic.Put(row, fromCol, graphbox.NewDivider(toCol, action.Message, style))
}

// Places a block
func (gb *graphicBuilder) putBlock(row *int, depth int, action *Block) {
	if action.Concurrent() {
		gb.putBlockSegmentsConcurrently(row, depth, action)
	} else {
		gb.putBlockSegmentsSequentially(row, depth, action)
	}
}

func (gb *graphicBuilder) putBlockSegmentsConcurrently(row *int, depth int, action *Block) {
	startRow := *row

	for _, seg := range action.Segments {
		thisRow := startRow
		gb.putItemsInSlice(&thisRow, depth+1, seg.SubItems)
		if thisRow > *row {
			*row = thisRow
		}
	}
	*row++
}

func getInnerRanksRecursive(subItems []SequenceItem) []int {
	ranks := []int{}
	for _, subItem := range subItems {
		if action, isAction := subItem.(*Action); isAction {
			ranks = append(ranks, action.From.rank)
			ranks = append(ranks, action.To.rank)
		} else if block, isBlock := subItem.(*Block); isBlock {
			for _, segment := range block.Segments {
				ranks = append(ranks, getInnerRanksRecursive(segment.SubItems)...)
			}
		}
	}
	return ranks
}

func (gb *graphicBuilder) getStartAndEndColsBasedOnContent(subItems []SequenceItem) (int, int) {
	startCol := 0
	endCol := gb.Graphic.Cols() - 1 // This needs to be the column of the last actor

	innerRanks := getInnerRanksRecursive(subItems)
	sort.Ints(innerRanks)
	if len(innerRanks) > 1 && innerRanks[0] != innerRanks[len(innerRanks)-1] {
		// +1 because actor rank and column values are offset by one
		startCol = innerRanks[0] + 1
		endCol = innerRanks[len(innerRanks)-1] + 1
	}

	return startCol, endCol
}

func (gb *graphicBuilder) putBlockSegmentsSequentially(row *int, depth int, action *Block) {
	style := gb.Style.Block

	var startRow, endRow int
	startRow = *row
	nestDepth := action.MaxNestDepth()

	for i, seg := range action.Segments {
		// To outline only the inner actors of the block we need to set...
		//  - startCol to the leftmost inner actor of the block
		//  - endCol to the rightmost inner actor of the block
		startCol, endCol := gb.getStartAndEndColsBasedOnContent(seg.SubItems)

		*row++
		gb.putItemsInSlice(row, depth+1, seg.SubItems)
		endRow = *row

		segPrefix := ""
		showPrefix := true

		switch seg.Type {
		case AltSegmentType:
			segPrefix = "alt"
		case ElseSegmentType:
			segPrefix = "alt"
			showPrefix = false
		case ParSegmentType:
			segPrefix = "par"
		case ParElseSegmentType:
			segPrefix = "par"
			showPrefix = false
		case OptSegmentType:
			segPrefix = "opt"
		case LoopSegmentType:
			segPrefix = "loop"
		}

		if seg.Prefix != "" {
			segPrefix = seg.Prefix
		}

		block := graphbox.NewBlock(endRow, endCol, nestDepth, i == len(action.Segments)-1,
			segPrefix, showPrefix, seg.Message, style)
		gb.Graphic.Put(startRow, startCol, block)

		startRow = endRow
	}
}

// Count the number of rows needed in the graphic
func (gb *graphicBuilder) calcRowsAndCols() (int, int) {
	cols := gb.determineActorInfo() + 1

	// 1 for the title, object header and object footer
	if len(gb.Diagram.Items) == 0 {
		return posObjectY + 3, cols
	} else {
		return gb.calcItemsInSlice(gb.Diagram.Items) + posObjectY + 2, cols
	}
}

// Determine actor information.  Returns the number of colums required
func (gb *graphicBuilder) determineActorInfo() int {
	gb.actorInfos = make([]actorInfo, len(gb.Diagram.Actors))

	// Allocate the columns
	cols := posObjectLeftX
	for _, actor := range gb.Diagram.Actors {
		colsRequiredByActor := 1
		actorCol := cols

		if actor.rank != -1 {
			if gb.actorInfos[actor.rank].ExtraLeftCol {
				colsRequiredByActor++
				actorCol++
			}
			if gb.actorInfos[actor.rank].ExtraRightCol {
				colsRequiredByActor++
			}

			gb.actorInfos[actor.rank].Col = actorCol
			cols += colsRequiredByActor
		}
	}

	return cols
}

// Add the object headers and footers
func (gb *graphicBuilder) addActors() {
	// TODO: Proper styling
	bottomRow := gb.Graphic.Rows() - 1
	for rank, actor := range gb.Diagram.Actors {
		var actorBoxPos graphbox.ActorBoxPos

		if rank == 0 {
			actorBoxPos = graphbox.LeftActorBox
		} else if rank == len(gb.Diagram.Actors)-1 {
			actorBoxPos = graphbox.RightActorBox
		} else {
			actorBoxPos = graphbox.MiddleActorBox
		}

		col := gb.colOfActor(actor)

		if actor.Lifeline {
			gb.Graphic.Put(posObjectY, col, &graphbox.LifeLine{
				TR: bottomRow,
				TC: col,
				Style: graphbox.LifeLineStyle{
					Color: actor.Color,
				},
			})
		}

		if actor.Icon != nil {
			actorIconStyle := gb.Style.ActorIconBox
			actorIconStyle.Color = actor.Color
			actorIconStyle.TextColor = actor.TextColor

			if actor.InHeader {
				gb.Graphic.Put(posObjectY, col, graphbox.NewActorIconBox(actor.Label, actor.Icon.graphboxIcon(), actorIconStyle, actorBoxPos|graphbox.TopActorBox))
			}
		} else {
			// Configure the style
			actorStyle := gb.Style.ActorBox
			actorStyle.Color = actor.Color
			actorStyle.TextColor = actor.TextColor

			if actor.InHeader {
				gb.Graphic.Put(posObjectY, col, graphbox.NewActorBox(actor.Label, actorStyle, actorBoxPos|graphbox.TopActorBox))
				if actor.InFooter {
					gb.Graphic.Put(bottomRow, col, graphbox.NewActorBox(actor.Label, actorStyle, actorBoxPos|graphbox.BottomActorBox))
				}
			} else {
				if actor.InFooter {
					// Use the TopActorBox as that performs the layout
					gb.Graphic.Put(bottomRow, col, graphbox.NewActorBox(actor.Label, actorStyle, actorBoxPos|graphbox.TopActorBox))
				}
			}
		}
	}
}

// Returns the column position of an actor
func (gb *graphicBuilder) colOfActor(actor *Actor) int {
	if actor == LeftOffsideActor {
		return 0
	} else if actor == RightOffsideActor {
		return gb.Graphic.Cols() - 1
	} else {
		return gb.actorInfos[actor.rank].Col
	}
}
<commit_msg>use the bounds of the all the sibling blocks to stop them misaligning<commit_after>package seqdiagram

import (
	"errors"
	"sort"

	"github.com/lmika/goseq/seqdiagram/graphbox"
)

// Various position offsets
const (
	posObjectLeftX = 1
	posObjectY     = 1
)

var graphboxArrowStemMapping = map[ArrowStem]graphbox.ActivityArrowStem{
	SolidArrowStem:  graphbox.SolidArrowStem,
	DashedArrowStem: graphbox.DashedArrowStem,
	ThickArrowStem:  graphbox.ThickArrowStem,
}

// Load the internal font
func mustLoadFont() *graphbox.TTFFont {
	font, err := loadInternalFont(dejaVuSansFont)
	if err != nil {
		panic(errors.New("Could not load internal font: " + dejaVuSansFont))
	}

	return font
}

// Information about a particular actor
type actorInfo struct {
	// Extra cols needed on the left or right
	ExtraLeftCol  bool
	ExtraRightCol bool

	// Actor column
	Col int
}

type graphicBuilder struct {
	Diagram *Diagram
	Graphic *graphbox.Graphic
	Style   *DiagramStyles

	actorInfos []actorInfo
}

func newGraphicBuilder(d *Diagram, style *DiagramStyles) (*graphicBuilder, error) {
	return &graphicBuilder{d, nil, style, nil}, nil
}

func (gb *graphicBuilder) buildGraphic() *graphbox.Graphic {
	rows, cols := gb.calcRowsAndCols()
	gb.Graphic = graphbox.NewGraphic(rows, cols)

	gb.Graphic.Margin = gb.Style.Margin
	gb.Graphic.ShowGrid = false

	gb.addActors()

	if len(gb.Diagram.Items) == 0 {
		gb.Graphic.Put(2, 0, &graphbox.Spacer{graphbox.Point{0, 64}})
	} else {
		row := 2
		gb.putItemsInSlice(&row, 0, gb.Diagram.Items)
	}

	// Add a title
	if gb.Diagram.Title != "" {
		gb.Graphic.Put(0, 0, graphbox.NewTitle(cols, gb.Diagram.Title, gb.Style.Title))
	}

	return gb.Graphic
}

// Place items in a slice.  This will update the rows pointer
func (gb *graphicBuilder) putItemsInSlice(row *int, depth int, items []SequenceItem) {
	for _, item := range items {
		switch itemDetails := item.(type) {
		case *Action:
			gb.putAction(*row, itemDetails)
		case *Note:
			gb.putNote(*row, itemDetails)
		case *Divider:
			gb.putDivider(*row, itemDetails)
		case *Block:
			gb.putBlock(row, depth, itemDetails)
		}

		*row += 1
	}
}

// Calculate rows in slice
func (gb *graphicBuilder) calcItemsInSlice(items []SequenceItem) int {
	rows := 0
	for _, item := range items {
		switch itemDetails := item.(type) {
		case *Block:
			if itemDetails.Concurrent() {
				maxRows := 0
				for _, seg := range itemDetails.Segments {
					rowsInSeg := gb.calcItemsInSlice(seg.SubItems) + 1
					if rowsInSeg > maxRows {
						maxRows = rowsInSeg
					}
				}
				rows += maxRows
			} else {
				for _, seg := range itemDetails.Segments {
					rows += gb.calcItemsInSlice(seg.SubItems) + 1
				}
			}
			rows += 1
		default:
			rows++
		}
	}
	return rows
}

// Places a note
func (gb *graphicBuilder) putNote(row int, note *Note) {
	if (note.Actor2 == nil) || (note.Actor1 == note.Actor2) {
		gb.putSingleActorNote(row, note.Actor1, note)
	} else {
		var leftActor, rightActor *Actor
		if gb.colOfActor(note.Actor1) < gb.colOfActor(note.Actor2) {
			leftActor, rightActor = note.Actor1, note.Actor2
		} else {
			leftActor, rightActor = note.Actor2, note.Actor1
		}

		switch note.Align {
		case OverNoteAlignment:
			gb.putMultiActorOverNote(row, leftActor, rightActor, note)
		case LeftNoteAlignment:
			gb.putSingleActorNote(row, leftActor, note)
		case RightNoteAlignment:
			gb.putSingleActorNote(row, rightActor, note)
		}
	}
}

// Places a note over a single actor
func (gb *graphicBuilder) putSingleActorNote(row int, actor *Actor, note *Note) {
	var pos graphbox.NoteBoxPos

	if note.Align == LeftNoteAlignment {
		pos = graphbox.LeftNotePos
	} else if note.Align == OverNoteAlignment {
		pos = graphbox.CenterNotePos
	} else if note.Align == RightNoteAlignment {
		pos = graphbox.RightNotePos
	}

	col := gb.colOfActor(actor)
	gb.Graphic.Put(row, col, graphbox.NewNoteBox(note.Message, gb.Style.NoteBox, pos))
}

// Places a note over a multiple actors.  This actually uses the divider graphics object
// with the style adopted from the note style
func (gb *graphicBuilder) putMultiActorOverNote(row int, leftActor *Actor, rightActor *Actor, note *Note) {
	dividerBox := graphbox.DividerStyle{
		Font:        gb.Style.NoteBox.Font,
		FontSize:    gb.Style.NoteBox.FontSize,
		Padding:     gb.Style.NoteBox.Padding,
		Margin:      gb.Style.NoteBox.Margin,
		TextPadding: graphbox.Point{0, 0},
		Shape:       graphbox.DSFramedRect,
		Overlap:     gb.Style.MultiNoteOverlap,
	}

	fromCol := gb.colOfActor(leftActor)
	toCol := gb.colOfActor(rightActor)

	// TODO: This was just to avoid bad styling of notes which reference 'left' and 'right'
	// This needs to be fixed using proper styling, instead of this hack.
	if fromCol == 0 {
		fromCol = 1
	}
	if toCol == gb.Graphic.Cols()-1 {
		toCol = gb.Graphic.Cols() - 2
	}

	gb.Graphic.Put(row, fromCol, graphbox.NewDivider(toCol, note.Message, dividerBox))
}

// Places an action
func (gb *graphicBuilder) putAction(row int, action *Action) {
	fromCol := gb.colOfActor(action.From)
	toCol := gb.colOfActor(action.To)

	style := gb.Style.ActivityLine

	style.ArrowHead = gb.Style.ArrowHeads[action.Arrow.Head] //graphboxArrowHeadMapping[action.Arrow.Head]
	style.ArrowStem = graphboxArrowStemMapping[action.Arrow.Stem]

	gb.Graphic.Put(row, fromCol, graphbox.NewActivityLine(toCol, fromCol == toCol, action.Message, style))
}

// Places a divider
func (gb *graphicBuilder) putDivider(row int, action *Divider) {
	fromCol := 0
	toCol := gb.Graphic.Cols() - 1
	style := gb.Style.Divider[action.Type]

	gb.Graphic.Put(row, fromCol, graphbox.NewDivider(toCol, action.Message, style))
}

// Places a block
func (gb *graphicBuilder) putBlock(row *int, depth int, action *Block) {
	if action.Concurrent() {
		gb.putBlockSegmentsConcurrently(row, depth, action)
	} else {
		gb.putBlockSegmentsSequentially(row, depth, action)
	}
}

func (gb *graphicBuilder) putBlockSegmentsConcurrently(row *int, depth int, action *Block) {
	startRow := *row

	for _, seg := range action.Segments {
		thisRow := startRow
		gb.putItemsInSlice(&thisRow, depth+1, seg.SubItems)
		if thisRow > *row {
			*row = thisRow
		}
	}
	*row++
}

func getInnerRanksRecursive(subItems []SequenceItem) []int {
	ranks := []int{}
	for _, subItem := range subItems {
		if action, isAction := subItem.(*Action); isAction {
			ranks = append(ranks, action.From.rank)
			ranks = append(ranks, action.To.rank)
		} else if block, isBlock := subItem.(*Block); isBlock {
			for _, segment := range block.Segments {
				ranks = append(ranks, getInnerRanksRecursive(segment.SubItems)...)
			}
		}
	}
	return ranks
}

func (gb *graphicBuilder) getStartAndEndColsBasedOnContent(subItems []SequenceItem) (int, int) {
	startCol := 0
	endCol := gb.Graphic.Cols() - 1 // This needs to be the column of the last actor

	innerRanks := getInnerRanksRecursive(subItems)
	sort.Ints(innerRanks)
	if len(innerRanks) > 1 && innerRanks[0] != innerRanks[len(innerRanks)-1] {
		// +1 because actor rank and column values are offset by one
		startCol = innerRanks[0] + 1
		endCol = innerRanks[len(innerRanks)-1] + 1
	}

	return startCol, endCol
}

func (gb *graphicBuilder) putBlockSegmentsSequentially(row *int, depth int, action *Block) {
	style := gb.Style.Block

	var startRow, endRow int
	startRow = *row
	nestDepth := action.MaxNestDepth()

	startCol := 0
	endCol := gb.Graphic.Cols() - 1 // This needs to be the column of the last actor

	// To outline only the inner actors of the sibling blocks we need to set...
	//  - startCol to the leftmost inner actor of the sibling blocks
	//  - endCol to the rightmost inner actor of the sibling blocks
	for _, seg := range action.Segments {
		segStartCol, segEndCol := gb.getStartAndEndColsBasedOnContent(seg.SubItems)

		if segStartCol < startCol || startCol == 0 {
			startCol = segStartCol
		}
		if segEndCol > endCol || endCol == gb.Graphic.Cols()-1 {
			endCol = segEndCol
		}
	}

	for i, seg := range action.Segments {
		*row++
		gb.putItemsInSlice(row, depth+1, seg.SubItems)
		endRow = *row

		segPrefix := ""
		showPrefix := true

		switch seg.Type {
		case AltSegmentType:
			segPrefix = "alt"
		case ElseSegmentType:
			segPrefix = "alt"
			showPrefix = false
		case ParSegmentType:
			segPrefix = "par"
		case ParElseSegmentType:
			segPrefix = "par"
			showPrefix = false
		case OptSegmentType:
			segPrefix = "opt"
		case LoopSegmentType:
			segPrefix = "loop"
		}

		if seg.Prefix != "" {
			segPrefix = seg.Prefix
		}

		block := graphbox.NewBlock(endRow, endCol, nestDepth, i == len(action.Segments)-1,
			segPrefix, showPrefix, seg.Message, style)
		gb.Graphic.Put(startRow, startCol, block)

		startRow = endRow
	}
}

// Count the number of rows needed in the graphic
func (gb *graphicBuilder) calcRowsAndCols() (int, int) {
	cols := gb.determineActorInfo() + 1

	// 1 for the title, object header and object footer
	if len(gb.Diagram.Items) == 0 {
		return posObjectY + 3, cols
	} else {
		return gb.calcItemsInSlice(gb.Diagram.Items) + posObjectY + 2, cols
	}
}

// Determine actor information.  Returns the number of colums required
func (gb *graphicBuilder) determineActorInfo() int {
	gb.actorInfos = make([]actorInfo, len(gb.Diagram.Actors))

	// Allocate the columns
	cols := posObjectLeftX
	for _, actor := range gb.Diagram.Actors {
		colsRequiredByActor := 1
		actorCol := cols

		if actor.rank != -1 {
			if gb.actorInfos[actor.rank].ExtraLeftCol {
				colsRequiredByActor++
				actorCol++
			}
			if gb.actorInfos[actor.rank].ExtraRightCol {
				colsRequiredByActor++
			}

			gb.actorInfos[actor.rank].Col = actorCol
			cols += colsRequiredByActor
		}
	}

	return cols
}

// Add the object headers and footers
func (gb *graphicBuilder) addActors() {
	// TODO: Proper styling
	bottomRow := gb.Graphic.Rows() - 1
	for rank, actor := range gb.Diagram.Actors {
		var actorBoxPos graphbox.ActorBoxPos

		if rank == 0 {
			actorBoxPos = graphbox.LeftActorBox
		} else if rank == len(gb.Diagram.Actors)-1 {
			actorBoxPos = graphbox.RightActorBox
		} else {
			actorBoxPos = graphbox.MiddleActorBox
		}

		col := gb.colOfActor(actor)

		if actor.Lifeline {
			gb.Graphic.Put(posObjectY, col, &graphbox.LifeLine{
				TR: bottomRow,
				TC: col,
				Style: graphbox.LifeLineStyle{
					Color: actor.Color,
				},
			})
		}

		if actor.Icon != nil {
			actorIconStyle := gb.Style.ActorIconBox
			actorIconStyle.Color = actor.Color
			actorIconStyle.TextColor = actor.TextColor

			if actor.InHeader {
				gb.Graphic.Put(posObjectY, col, graphbox.NewActorIconBox(actor.Label, actor.Icon.graphboxIcon(), actorIconStyle, actorBoxPos|graphbox.TopActorBox))
			}
		} else {
			// Configure the style
			actorStyle := gb.Style.ActorBox
			actorStyle.Color = actor.Color
			actorStyle.TextColor = actor.TextColor

			if actor.InHeader {
				gb.Graphic.Put(posObjectY, col, graphbox.NewActorBox(actor.Label, actorStyle, actorBoxPos|graphbox.TopActorBox))
				if actor.InFooter {
					gb.Graphic.Put(bottomRow, col, graphbox.NewActorBox(actor.Label, actorStyle, actorBoxPos|graphbox.BottomActorBox))
				}
			} else {
				if actor.InFooter {
					// Use the TopActorBox as that performs the layout
					gb.Graphic.Put(bottomRow, col, graphbox.NewActorBox(actor.Label, actorStyle, actorBoxPos|graphbox.TopActorBox))
				}
			}
		}
	}
}

// Returns the column position of an actor
func (gb *graphicBuilder) colOfActor(actor *Actor) int {
	if actor == LeftOffsideActor {
		return 0
	} else if actor == RightOffsideActor {
		return gb.Graphic.Cols() - 1
	} else {
		return gb.actorInfos[actor.rank].Col
	}
}
<|endoftext|>


<commit_before>package http

import (
	"fmt"
	"net/http"
	"strings"
	"time"

	"github.com/oinume/lekcije/server/context_data"
	"github.com/oinume/lekcije/server/errors"
	"github.com/oinume/lekcije/server/event_logger"
	"github.com/oinume/lekcije/server/fetcher"
	"github.com/oinume/lekcije/server/interfaces/http/flash_message"
	"github.com/oinume/lekcije/server/logger"
	"github.com/oinume/lekcije/server/model"
	"github.com/oinume/lekcije/server/util"
)

const (
	followedMessage                = "フォローしました！"
	unfollowedMessage              = "削除しました！"
	emptyTeacherURLMessage         = "講師のURLまたはIDを入力して下さい"
	invalidTeacherURLMessage       = "正しい講師のURLまたはIDを入力して下さい"
	reachedMaxFollowTeacherMessage = "フォロー可能な上限数(%d)を超えました"
)

var _ = fmt.Printf

func (s *server) getMeHandler() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		s.getMe(w, r)
	}
}

func (s *server) getMe(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	user := context_data.MustLoggedInUser(ctx)
	t := ParseHTMLTemplates(TemplatePath("me/index.html"))
	type Data struct {
		commonTemplateData
		ShowTutorial bool
		Teachers     []*model.Teacher
		MPlan        *model.MPlan
	}
	data := &Data{
		commonTemplateData: s.getCommonTemplateData(r, true, user.ID),
	}
	data.ShowTutorial = !user.FollowedTeacherAt.Valid

	mPlanService := model.NewMPlanService(s.db)
	plan, err := mPlanService.FindByPK(user.PlanID)
	if err != nil {
		internalServerError(w, err)
		return
	}
	data.MPlan = plan

	followingTeacherService := model.NewFollowingTeacherService(s.db)
	teachers, err := followingTeacherService.FindTeachersByUserID(user.ID)
	if err != nil {
		internalServerError(w, err)
		return
	}
	data.Teachers = teachers

	if err := t.Execute(w, data); err != nil {
		internalServerError(w, errors.NewInternalError(
			errors.WithError(err),
			errors.WithMessage("Failed to template.Execute()"),
		))
		return
	}
}

func (s *server) postMeFollowingTeachersCreateHandler() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		s.postMeFollowingTeachersCreate(w, r)
	}
}

func (s *server) postMeFollowingTeachersCreate(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	user := context_data.MustLoggedInUser(ctx)
	teacherIDsOrUrl := r.FormValue("teacherIdsOrUrl")
	if teacherIDsOrUrl == "" {
		e := flash_message.New(flash_message.KindWarning, emptyTeacherURLMessage)
		if err := s.flashMessageStore.Save(e); err != nil {
			internalServerError(w, err)
			return
		}
		http.Redirect(w, r, "/me?"+e.AsURLQueryString(), http.StatusFound)
		return
	}

	teachers, err := model.NewTeachersFromIDsOrURL(teacherIDsOrUrl)
	if err != nil {
		e := flash_message.New(flash_message.KindWarning, invalidTeacherURLMessage)
		if err := s.flashMessageStore.Save(e); err != nil {
			internalServerError(w, err)
			return
		}
		http.Redirect(w, r, "/me?"+e.AsURLQueryString(), http.StatusFound)
		return
	}

	followingTeacherService := model.NewFollowingTeacherService(s.db)
	reachesLimit, err := followingTeacherService.ReachesFollowingTeacherLimit(user.ID, len(teachers))
	if err != nil {
		internalServerError(w, err)
		return
	}
	if reachesLimit {
		e := flash_message.New(
			flash_message.KindWarning,
			fmt.Sprintf(reachedMaxFollowTeacherMessage, model.MaxFollowTeacherCount),
		)
		if err := s.flashMessageStore.Save(e); err != nil {
			internalServerError(w, err)
			return
		}
		http.Redirect(w, r, "/me?"+e.AsURLQueryString(), http.StatusFound)
		return
	}

	// Update user.followed_teacher_at when first time to follow teachers.
	// the column is used for showing tutorial or not.
	updateFollowedTeacherAt := false
	if !user.FollowedTeacherAt.Valid {
		userService := model.NewUserService(s.db)
		if err := userService.UpdateFollowedTeacherAt(user); err != nil {
			internalServerError(w, err)
			return
		}
		updateFollowedTeacherAt = true
	}

	mCountryService := model.NewMCountryService(s.db)
	// TODO: preload
	mCountries, err := mCountryService.LoadAll()
	if err != nil {
		internalServerError(w, err)
		return
	}
	fetcher := fetcher.NewLessonFetcher(nil, 1, false, mCountries, logger.App)
	defer fetcher.Close()
	now := time.Now().UTC()
	teacherIDs := make([]string, 0, len(teachers))
	for _, t := range teachers {
		teacher, _, err := fetcher.Fetch(t.ID)
		if err != nil {
			if errors.IsNotFound(err) {
				continue
			}
			internalServerError(w, err)
			return
		}

		if _, err := followingTeacherService.FollowTeacher(user.ID, teacher, now); err != nil {
			internalServerError(w, err)
			return
		}
		teacherIDs = append(teacherIDs, fmt.Sprint(t.ID))
	}

	go event_logger.SendGAMeasurementEvent2(
		event_logger.MustGAMeasurementEventValues(r.Context()),
		event_logger.CategoryFollowingTeacher, "follow",
		strings.Join(teacherIDs, ","), int64(len(teacherIDs)), user.ID,
	)
	if updateFollowedTeacherAt {
		go event_logger.SendGAMeasurementEvent2(
			event_logger.MustGAMeasurementEventValues(r.Context()),
			event_logger.CategoryUser, "followFirstTime",
			fmt.Sprint(user.ID), 0, user.ID,
		)
	}

	successMessage := flash_message.New(flash_message.KindSuccess, followedMessage)
	if err := s.flashMessageStore.Save(successMessage); err != nil {
		internalServerError(w, err)
		return
	}

	http.Redirect(w, r, "/me?"+successMessage.AsURLQueryString(), http.StatusFound)
}

func (s *server) postMeFollowingTeachersDeleteHandler() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		s.postMeFollowingTeachersDelete(w, r)
	}
}

func (s *server) postMeFollowingTeachersDelete(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	user := context_data.MustLoggedInUser(ctx)
	if err := r.ParseForm(); err != nil {
		http.Redirect(w, r, "/", http.StatusFound)
		return
	}
	teacherIDs := r.Form["teacherIds"]
	if len(teacherIDs) == 0 {
		http.Redirect(w, r, "/", http.StatusFound)
		return
	}

	followingTeacherService := model.NewFollowingTeacherService(s.db)
	_, err := followingTeacherService.DeleteTeachersByUserIDAndTeacherIDs(
		user.ID,
		util.StringToUint32Slice(teacherIDs...),
	)
	if err != nil {
		internalServerError(w, errors.NewInternalError(
			errors.WithError(err),
			errors.WithMessage("Failed to delete teachers"),
			errors.WithResource(errors.NewResource("following_teacher_service", "teacherIDs", teacherIDs)),
		))
		return
	}
	go event_logger.SendGAMeasurementEvent2(
		event_logger.MustGAMeasurementEventValues(r.Context()),
		event_logger.CategoryFollowingTeacher, "unfollow",
		strings.Join(teacherIDs, ","), int64(len(teacherIDs)), user.ID,
	)

	successMessage := flash_message.New(flash_message.KindSuccess, unfollowedMessage)
	if err := s.flashMessageStore.Save(successMessage); err != nil {
		internalServerError(w, err)
		return
	}

	http.Redirect(w, r, "/me?"+successMessage.AsURLQueryString(), http.StatusFound)
}

func (s *server) getMeSettingHandler() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		s.getMeSetting(w, r)
	}
}

func (s *server) getMeSetting(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	user := context_data.MustLoggedInUser(ctx)
	t := ParseHTMLTemplates(TemplatePath("me/setting.html"))
	type Data struct {
		commonTemplateData
		Email string
	}
	data := &Data{
		commonTemplateData: s.getCommonTemplateData(r, true, user.ID),
		Email:              user.Email,
	}

	if err := t.Execute(w, data); err != nil {
		internalServerError(w, errors.NewInternalError(
			errors.WithError(err),
			errors.WithMessage("Failed to template.Execute()"),
		))
		return
	}
}

func (s *server) getMeLogoutHandler() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		s.getMeLogout(w, r)
	}
}

func (s *server) getMeLogout(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	user, err := context_data.GetLoggedInUser(ctx)
	if err != nil {
		http.Redirect(w, r, "/", http.StatusFound)
		return
	}

	cookie, err := r.Cookie(APITokenCookieName)
	if err != nil {
		internalServerError(w, errors.NewInternalError(
			errors.WithError(err),
			errors.WithMessage("Failed to get token cookie"),
		))
		return
	}
	token := cookie.Value
	cookieToDelete := &http.Cookie{
		Name:     APITokenCookieName,
		Value:    "",
		Path:     "/",
		MaxAge:   -1,
		HttpOnly: false,
	}
	http.SetCookie(w, cookieToDelete)
	userAPITokenService := model.NewUserAPITokenService(s.db)
	if err := userAPITokenService.DeleteByUserIDAndToken(user.ID, token); err != nil {
		internalServerError(w, err)
		return
	}
	go event_logger.SendGAMeasurementEvent2(
		event_logger.MustGAMeasurementEventValues(r.Context()),
		event_logger.CategoryUser, "logout", "", 0, user.ID,
	)

	http.Redirect(w, r, "/", http.StatusFound)
}
<commit_msg>Set open_notification_at when first time following<commit_after>package http

import (
	"fmt"
	"net/http"
	"strings"
	"time"

	"github.com/oinume/lekcije/server/context_data"
	"github.com/oinume/lekcije/server/errors"
	"github.com/oinume/lekcije/server/event_logger"
	"github.com/oinume/lekcije/server/fetcher"
	"github.com/oinume/lekcije/server/interfaces/http/flash_message"
	"github.com/oinume/lekcije/server/logger"
	"github.com/oinume/lekcije/server/model"
	"github.com/oinume/lekcije/server/util"
)

const (
	followedMessage                = "フォローしました！"
	unfollowedMessage              = "削除しました！"
	emptyTeacherURLMessage         = "講師のURLまたはIDを入力して下さい"
	invalidTeacherURLMessage       = "正しい講師のURLまたはIDを入力して下さい"
	reachedMaxFollowTeacherMessage = "フォロー可能な上限数(%d)を超えました"
)

var _ = fmt.Printf

func (s *server) getMeHandler() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		s.getMe(w, r)
	}
}

func (s *server) getMe(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	user := context_data.MustLoggedInUser(ctx)
	t := ParseHTMLTemplates(TemplatePath("me/index.html"))
	type Data struct {
		commonTemplateData
		ShowTutorial bool
		Teachers     []*model.Teacher
		MPlan        *model.MPlan
	}
	data := &Data{
		commonTemplateData: s.getCommonTemplateData(r, true, user.ID),
	}
	data.ShowTutorial = !user.FollowedTeacherAt.Valid

	mPlanService := model.NewMPlanService(s.db)
	plan, err := mPlanService.FindByPK(user.PlanID)
	if err != nil {
		internalServerError(w, err)
		return
	}
	data.MPlan = plan

	followingTeacherService := model.NewFollowingTeacherService(s.db)
	teachers, err := followingTeacherService.FindTeachersByUserID(user.ID)
	if err != nil {
		internalServerError(w, err)
		return
	}
	data.Teachers = teachers

	if err := t.Execute(w, data); err != nil {
		internalServerError(w, errors.NewInternalError(
			errors.WithError(err),
			errors.WithMessage("Failed to template.Execute()"),
		))
		return
	}
}

func (s *server) postMeFollowingTeachersCreateHandler() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		s.postMeFollowingTeachersCreate(w, r)
	}
}

func (s *server) postMeFollowingTeachersCreate(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	user := context_data.MustLoggedInUser(ctx)
	teacherIDsOrUrl := r.FormValue("teacherIdsOrUrl")
	if teacherIDsOrUrl == "" {
		e := flash_message.New(flash_message.KindWarning, emptyTeacherURLMessage)
		if err := s.flashMessageStore.Save(e); err != nil {
			internalServerError(w, err)
			return
		}
		http.Redirect(w, r, "/me?"+e.AsURLQueryString(), http.StatusFound)
		return
	}

	teachers, err := model.NewTeachersFromIDsOrURL(teacherIDsOrUrl)
	if err != nil {
		e := flash_message.New(flash_message.KindWarning, invalidTeacherURLMessage)
		if err := s.flashMessageStore.Save(e); err != nil {
			internalServerError(w, err)
			return
		}
		http.Redirect(w, r, "/me?"+e.AsURLQueryString(), http.StatusFound)
		return
	}

	followingTeacherService := model.NewFollowingTeacherService(s.db)
	reachesLimit, err := followingTeacherService.ReachesFollowingTeacherLimit(user.ID, len(teachers))
	if err != nil {
		internalServerError(w, err)
		return
	}
	if reachesLimit {
		e := flash_message.New(
			flash_message.KindWarning,
			fmt.Sprintf(reachedMaxFollowTeacherMessage, model.MaxFollowTeacherCount),
		)
		if err := s.flashMessageStore.Save(e); err != nil {
			internalServerError(w, err)
			return
		}
		http.Redirect(w, r, "/me?"+e.AsURLQueryString(), http.StatusFound)
		return
	}

	// Update user.followed_teacher_at when first time to follow teachers.
	// the column is used for showing tutorial or not.
	updateFollowedTeacherAt := false
	if !user.FollowedTeacherAt.Valid {
		userService := model.NewUserService(s.db)
		// TODO: 1回でまとめて更新する
		if err := userService.UpdateFollowedTeacherAt(user); err != nil {
			internalServerError(w, err)
			return
		}
		if err := userService.UpdateOpenNotificationAt(user.ID, time.Now().UTC()); err != nil {
			internalServerError(w, err)
			return
		}
		updateFollowedTeacherAt = true
	}

	mCountryService := model.NewMCountryService(s.db)
	// TODO: preload
	mCountries, err := mCountryService.LoadAll()
	if err != nil {
		internalServerError(w, err)
		return
	}
	fetcher := fetcher.NewLessonFetcher(nil, 1, false, mCountries, logger.App)
	defer fetcher.Close()
	now := time.Now().UTC()
	teacherIDs := make([]string, 0, len(teachers))
	for _, t := range teachers {
		teacher, _, err := fetcher.Fetch(t.ID)
		if err != nil {
			if errors.IsNotFound(err) {
				continue
			}
			internalServerError(w, err)
			return
		}

		if _, err := followingTeacherService.FollowTeacher(user.ID, teacher, now); err != nil {
			internalServerError(w, err)
			return
		}
		teacherIDs = append(teacherIDs, fmt.Sprint(t.ID))
	}

	go event_logger.SendGAMeasurementEvent2(
		event_logger.MustGAMeasurementEventValues(r.Context()),
		event_logger.CategoryFollowingTeacher, "follow",
		strings.Join(teacherIDs, ","), int64(len(teacherIDs)), user.ID,
	)
	if updateFollowedTeacherAt {
		go event_logger.SendGAMeasurementEvent2(
			event_logger.MustGAMeasurementEventValues(r.Context()),
			event_logger.CategoryUser, "followFirstTime",
			fmt.Sprint(user.ID), 0, user.ID,
		)
	}

	successMessage := flash_message.New(flash_message.KindSuccess, followedMessage)
	if err := s.flashMessageStore.Save(successMessage); err != nil {
		internalServerError(w, err)
		return
	}

	http.Redirect(w, r, "/me?"+successMessage.AsURLQueryString(), http.StatusFound)
}

func (s *server) postMeFollowingTeachersDeleteHandler() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		s.postMeFollowingTeachersDelete(w, r)
	}
}

func (s *server) postMeFollowingTeachersDelete(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	user := context_data.MustLoggedInUser(ctx)
	if err := r.ParseForm(); err != nil {
		http.Redirect(w, r, "/", http.StatusFound)
		return
	}
	teacherIDs := r.Form["teacherIds"]
	if len(teacherIDs) == 0 {
		http.Redirect(w, r, "/", http.StatusFound)
		return
	}

	followingTeacherService := model.NewFollowingTeacherService(s.db)
	_, err := followingTeacherService.DeleteTeachersByUserIDAndTeacherIDs(
		user.ID,
		util.StringToUint32Slice(teacherIDs...),
	)
	if err != nil {
		internalServerError(w, errors.NewInternalError(
			errors.WithError(err),
			errors.WithMessage("Failed to delete teachers"),
			errors.WithResource(errors.NewResource("following_teacher_service", "teacherIDs", teacherIDs)),
		))
		return
	}
	go event_logger.SendGAMeasurementEvent2(
		event_logger.MustGAMeasurementEventValues(r.Context()),
		event_logger.CategoryFollowingTeacher, "unfollow",
		strings.Join(teacherIDs, ","), int64(len(teacherIDs)), user.ID,
	)

	successMessage := flash_message.New(flash_message.KindSuccess, unfollowedMessage)
	if err := s.flashMessageStore.Save(successMessage); err != nil {
		internalServerError(w, err)
		return
	}

	http.Redirect(w, r, "/me?"+successMessage.AsURLQueryString(), http.StatusFound)
}

func (s *server) getMeSettingHandler() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		s.getMeSetting(w, r)
	}
}

func (s *server) getMeSetting(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	user := context_data.MustLoggedInUser(ctx)
	t := ParseHTMLTemplates(TemplatePath("me/setting.html"))
	type Data struct {
		commonTemplateData
		Email string
	}
	data := &Data{
		commonTemplateData: s.getCommonTemplateData(r, true, user.ID),
		Email:              user.Email,
	}

	if err := t.Execute(w, data); err != nil {
		internalServerError(w, errors.NewInternalError(
			errors.WithError(err),
			errors.WithMessage("Failed to template.Execute()"),
		))
		return
	}
}

func (s *server) getMeLogoutHandler() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		s.getMeLogout(w, r)
	}
}

func (s *server) getMeLogout(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	user, err := context_data.GetLoggedInUser(ctx)
	if err != nil {
		http.Redirect(w, r, "/", http.StatusFound)
		return
	}

	cookie, err := r.Cookie(APITokenCookieName)
	if err != nil {
		internalServerError(w, errors.NewInternalError(
			errors.WithError(err),
			errors.WithMessage("Failed to get token cookie"),
		))
		return
	}
	token := cookie.Value
	cookieToDelete := &http.Cookie{
		Name:     APITokenCookieName,
		Value:    "",
		Path:     "/",
		MaxAge:   -1,
		HttpOnly: false,
	}
	http.SetCookie(w, cookieToDelete)
	userAPITokenService := model.NewUserAPITokenService(s.db)
	if err := userAPITokenService.DeleteByUserIDAndToken(user.ID, token); err != nil {
		internalServerError(w, err)
		return
	}
	go event_logger.SendGAMeasurementEvent2(
		event_logger.MustGAMeasurementEventValues(r.Context()),
		event_logger.CategoryUser, "logout", "", 0, user.ID,
	)

	http.Redirect(w, r, "/", http.StatusFound)
}
<|endoftext|>


<commit_before>package restic_test

import (
	"bytes"
	"io"
	"io/ioutil"
	"os"
	"testing"

	"github.com/restic/restic"
	"github.com/restic/restic/chunker"
)

func TestEncryptDecrypt(t *testing.T) {
	s := setupBackend(t)
	defer teardownBackend(t, s)
	k := setupKey(t, s, testPassword)

	tests := []int{5, 23, 2<<18 + 23, 1 << 20}
	if *testLargeCrypto {
		tests = append(tests, 7<<20+123)
	}

	for _, size := range tests {
		data := make([]byte, size)
		_, err := io.ReadFull(randomReader(42, size), data)
		ok(t, err)

		ciphertext := restic.GetChunkBuf("TestEncryptDecrypt")
		n, err := k.Encrypt(ciphertext, data)
		ok(t, err)

		plaintext, err := k.Decrypt(nil, ciphertext[:n])
		ok(t, err)

		restic.FreeChunkBuf("TestEncryptDecrypt", ciphertext)

		equals(t, plaintext, data)
	}
}

func TestSmallBuffer(t *testing.T) {
	s := setupBackend(t)
	defer teardownBackend(t, s)
	k := setupKey(t, s, testPassword)

	size := 600
	data := make([]byte, size)
	f, err := os.Open("/dev/urandom")
	ok(t, err)

	_, err = io.ReadFull(f, data)
	ok(t, err)

	ciphertext := make([]byte, size/2)
	_, err = k.Encrypt(ciphertext, data)
	// this must throw an error, since the target slice is too small
	assert(t, err != nil && err == restic.ErrBufferTooSmall,
		"expected restic.ErrBufferTooSmall, got %#v", err)
}

func TestLargeEncrypt(t *testing.T) {
	if !*testLargeCrypto {
		t.SkipNow()
	}

	s := setupBackend(t)
	defer teardownBackend(t, s)
	k := setupKey(t, s, testPassword)

	for _, size := range []int{chunker.MaxSize, chunker.MaxSize + 1, chunker.MaxSize + 1<<20} {
		data := make([]byte, size)
		f, err := os.Open("/dev/urandom")
		ok(t, err)

		_, err = io.ReadFull(f, data)
		ok(t, err)

		ciphertext := make([]byte, size+restic.CiphertextExtension)
		n, err := k.Encrypt(ciphertext, data)
		ok(t, err)

		plaintext, err := k.Decrypt([]byte{}, ciphertext[:n])
		ok(t, err)

		equals(t, plaintext, data)
	}
}

func BenchmarkEncryptWriter(b *testing.B) {
	size := 8 << 20 // 8MiB
	rd := randomReader(23, size)

	be := setupBackend(b)
	defer teardownBackend(b, be)
	k := setupKey(b, be, testPassword)

	b.ResetTimer()
	b.SetBytes(int64(size))

	for i := 0; i < b.N; i++ {
		rd.Seek(0, 0)
		wr := k.EncryptTo(ioutil.Discard)
		_, err := io.Copy(wr, rd)
		ok(b, err)
	}
}

func BenchmarkEncrypt(b *testing.B) {
	size := 8 << 20 // 8MiB
	data := make([]byte, size)

	be := setupBackend(b)
	defer teardownBackend(b, be)
	k := setupKey(b, be, testPassword)

	buf := make([]byte, len(data)+restic.CiphertextExtension)

	b.ResetTimer()
	b.SetBytes(int64(size))

	for i := 0; i < b.N; i++ {
		_, err := k.Encrypt(buf, data)
		ok(b, err)
	}
}

func BenchmarkDecryptReader(b *testing.B) {
	be := setupBackend(b)
	defer teardownBackend(b, be)
	k := setupKey(b, be, testPassword)

	size := 8 << 20 // 8MiB
	buf := get_random(23, size)

	ciphertext := make([]byte, len(buf)+restic.CiphertextExtension)
	_, err := k.Encrypt(ciphertext, buf)
	ok(b, err)

	rd := bytes.NewReader(ciphertext)

	b.ResetTimer()
	b.SetBytes(int64(size))

	for i := 0; i < b.N; i++ {
		rd.Seek(0, 0)
		decRd, err := k.DecryptFrom(rd)
		ok(b, err)

		_, err = io.Copy(ioutil.Discard, decRd)
		ok(b, err)
	}
}

func BenchmarkEncryptDecryptReader(b *testing.B) {
	be := setupBackend(b)
	defer teardownBackend(b, be)
	k := setupKey(b, be, testPassword)

	size := 8 << 20 // 8MiB
	rd := randomReader(23, size)

	b.ResetTimer()
	b.SetBytes(int64(size))

	buf := bytes.NewBuffer(nil)
	for i := 0; i < b.N; i++ {
		rd.Seek(0, 0)
		buf.Reset()
		wr := k.EncryptTo(buf)
		_, err := io.Copy(wr, rd)
		ok(b, err)
		ok(b, wr.Close())

		r, err := k.DecryptFrom(buf)
		ok(b, err)

		_, err = io.Copy(ioutil.Discard, r)
		ok(b, err)
	}

	restic.PoolAlloc()
}

func BenchmarkDecrypt(b *testing.B) {
	size := 8 << 20 // 8MiB
	data := make([]byte, size)

	s := setupBackend(b)
	defer teardownBackend(b, s)
	k := setupKey(b, s, testPassword)

	ciphertext := restic.GetChunkBuf("BenchmarkDecrypt")
	defer restic.FreeChunkBuf("BenchmarkDecrypt", ciphertext)
	plaintext := restic.GetChunkBuf("BenchmarkDecrypt")
	defer restic.FreeChunkBuf("BenchmarkDecrypt", plaintext)

	n, err := k.Encrypt(ciphertext, data)
	ok(b, err)

	b.ResetTimer()
	b.SetBytes(int64(size))

	for i := 0; i < b.N; i++ {
		plaintext, err = k.Decrypt(plaintext, ciphertext[:n])
		ok(b, err)
	}
}

func TestEncryptStreamWriter(t *testing.T) {
	s := setupBackend(t)
	defer teardownBackend(t, s)
	k := setupKey(t, s, testPassword)

	tests := []int{5, 23, 2<<18 + 23, 1 << 20}
	if *testLargeCrypto {
		tests = append(tests, 7<<20+123)
	}

	for _, size := range tests {
		data := make([]byte, size)
		_, err := io.ReadFull(randomReader(42, size), data)
		ok(t, err)

		ciphertext := bytes.NewBuffer(nil)
		wr := k.EncryptTo(ciphertext)

		_, err = io.Copy(wr, bytes.NewReader(data))
		ok(t, err)
		ok(t, wr.Close())

		l := len(data) + restic.CiphertextExtension
		assert(t, len(ciphertext.Bytes()) == l,
			"wrong ciphertext length: expected %d, got %d",
			l, len(ciphertext.Bytes()))

		// decrypt with default function
		plaintext, err := k.Decrypt([]byte{}, ciphertext.Bytes())
		ok(t, err)
		assert(t, bytes.Equal(data, plaintext),
			"wrong plaintext after decryption: expected %02x, got %02x",
			data, plaintext)
	}
}

func TestDecryptStreamReader(t *testing.T) {
	s := setupBackend(t)
	defer teardownBackend(t, s)
	k := setupKey(t, s, testPassword)

	tests := []int{5, 23, 2<<18 + 23, 1 << 20}
	if *testLargeCrypto {
		tests = append(tests, 7<<20+123)
	}

	for _, size := range tests {
		data := make([]byte, size)
		_, err := io.ReadFull(randomReader(42, size), data)
		ok(t, err)

		ciphertext := make([]byte, size+restic.CiphertextExtension)

		// encrypt with default function
		n, err := k.Encrypt(ciphertext, data)
		ok(t, err)
		assert(t, n == len(data)+restic.CiphertextExtension,
			"wrong number of bytes returned after encryption: expected %d, got %d",
			len(data)+restic.CiphertextExtension, n)

		rd, err := k.DecryptFrom(bytes.NewReader(ciphertext))
		ok(t, err)

		plaintext, err := ioutil.ReadAll(rd)
		ok(t, err)

		assert(t, bytes.Equal(data, plaintext),
			"wrong plaintext after decryption: expected %02x, got %02x",
			data, plaintext)
	}
}

func TestEncryptWriter(t *testing.T) {
	s := setupBackend(t)
	defer teardownBackend(t, s)
	k := setupKey(t, s, testPassword)

	tests := []int{5, 23, 2<<18 + 23, 1 << 20}
	if *testLargeCrypto {
		tests = append(tests, 7<<20+123)
	}

	for _, size := range tests {
		data := make([]byte, size)
		_, err := io.ReadFull(randomReader(42, size), data)
		ok(t, err)

		buf := bytes.NewBuffer(nil)
		wr := k.EncryptTo(buf)

		_, err = io.Copy(wr, bytes.NewReader(data))
		ok(t, err)
		ok(t, wr.Close())

		ciphertext := buf.Bytes()

		l := len(data) + restic.CiphertextExtension
		assert(t, len(ciphertext) == l,
			"wrong ciphertext length: expected %d, got %d",
			l, len(ciphertext))

		// decrypt with default function
		plaintext, err := k.Decrypt([]byte{}, ciphertext)
		ok(t, err)
		assert(t, bytes.Equal(data, plaintext),
			"wrong plaintext after decryption: expected %02x, got %02x",
			data, plaintext)
	}
}
<commit_msg>Fix benchmark for EncryptWriter<commit_after>package restic_test

import (
	"bytes"
	"io"
	"io/ioutil"
	"os"
	"testing"

	"github.com/restic/restic"
	"github.com/restic/restic/chunker"
)

func TestEncryptDecrypt(t *testing.T) {
	s := setupBackend(t)
	defer teardownBackend(t, s)
	k := setupKey(t, s, testPassword)

	tests := []int{5, 23, 2<<18 + 23, 1 << 20}
	if *testLargeCrypto {
		tests = append(tests, 7<<20+123)
	}

	for _, size := range tests {
		data := make([]byte, size)
		_, err := io.ReadFull(randomReader(42, size), data)
		ok(t, err)

		ciphertext := restic.GetChunkBuf("TestEncryptDecrypt")
		n, err := k.Encrypt(ciphertext, data)
		ok(t, err)

		plaintext, err := k.Decrypt(nil, ciphertext[:n])
		ok(t, err)

		restic.FreeChunkBuf("TestEncryptDecrypt", ciphertext)

		equals(t, plaintext, data)
	}
}

func TestSmallBuffer(t *testing.T) {
	s := setupBackend(t)
	defer teardownBackend(t, s)
	k := setupKey(t, s, testPassword)

	size := 600
	data := make([]byte, size)
	f, err := os.Open("/dev/urandom")
	ok(t, err)

	_, err = io.ReadFull(f, data)
	ok(t, err)

	ciphertext := make([]byte, size/2)
	_, err = k.Encrypt(ciphertext, data)
	// this must throw an error, since the target slice is too small
	assert(t, err != nil && err == restic.ErrBufferTooSmall,
		"expected restic.ErrBufferTooSmall, got %#v", err)
}

func TestLargeEncrypt(t *testing.T) {
	if !*testLargeCrypto {
		t.SkipNow()
	}

	s := setupBackend(t)
	defer teardownBackend(t, s)
	k := setupKey(t, s, testPassword)

	for _, size := range []int{chunker.MaxSize, chunker.MaxSize + 1, chunker.MaxSize + 1<<20} {
		data := make([]byte, size)
		f, err := os.Open("/dev/urandom")
		ok(t, err)

		_, err = io.ReadFull(f, data)
		ok(t, err)

		ciphertext := make([]byte, size+restic.CiphertextExtension)
		n, err := k.Encrypt(ciphertext, data)
		ok(t, err)

		plaintext, err := k.Decrypt([]byte{}, ciphertext[:n])
		ok(t, err)

		equals(t, plaintext, data)
	}
}

func BenchmarkEncryptWriter(b *testing.B) {
	size := 8 << 20 // 8MiB
	rd := randomReader(23, size)

	be := setupBackend(b)
	defer teardownBackend(b, be)
	k := setupKey(b, be, testPassword)

	b.ResetTimer()
	b.SetBytes(int64(size))

	for i := 0; i < b.N; i++ {
		rd.Seek(0, 0)
		wr := k.EncryptTo(ioutil.Discard)
		_, err := io.Copy(wr, rd)
		ok(b, err)
		ok(b, wr.Close())
	}
}

func BenchmarkEncrypt(b *testing.B) {
	size := 8 << 20 // 8MiB
	data := make([]byte, size)

	be := setupBackend(b)
	defer teardownBackend(b, be)
	k := setupKey(b, be, testPassword)

	buf := make([]byte, len(data)+restic.CiphertextExtension)

	b.ResetTimer()
	b.SetBytes(int64(size))

	for i := 0; i < b.N; i++ {
		_, err := k.Encrypt(buf, data)
		ok(b, err)
	}
}

func BenchmarkDecryptReader(b *testing.B) {
	be := setupBackend(b)
	defer teardownBackend(b, be)
	k := setupKey(b, be, testPassword)

	size := 8 << 20 // 8MiB
	buf := get_random(23, size)

	ciphertext := make([]byte, len(buf)+restic.CiphertextExtension)
	_, err := k.Encrypt(ciphertext, buf)
	ok(b, err)

	rd := bytes.NewReader(ciphertext)

	b.ResetTimer()
	b.SetBytes(int64(size))

	for i := 0; i < b.N; i++ {
		rd.Seek(0, 0)
		decRd, err := k.DecryptFrom(rd)
		ok(b, err)

		_, err = io.Copy(ioutil.Discard, decRd)
		ok(b, err)
	}
}

func BenchmarkEncryptDecryptReader(b *testing.B) {
	be := setupBackend(b)
	defer teardownBackend(b, be)
	k := setupKey(b, be, testPassword)

	size := 8 << 20 // 8MiB
	rd := randomReader(23, size)

	b.ResetTimer()
	b.SetBytes(int64(size))

	buf := bytes.NewBuffer(nil)
	for i := 0; i < b.N; i++ {
		rd.Seek(0, 0)
		buf.Reset()
		wr := k.EncryptTo(buf)
		_, err := io.Copy(wr, rd)
		ok(b, err)
		ok(b, wr.Close())

		r, err := k.DecryptFrom(buf)
		ok(b, err)

		_, err = io.Copy(ioutil.Discard, r)
		ok(b, err)
	}

	restic.PoolAlloc()
}

func BenchmarkDecrypt(b *testing.B) {
	size := 8 << 20 // 8MiB
	data := make([]byte, size)

	s := setupBackend(b)
	defer teardownBackend(b, s)
	k := setupKey(b, s, testPassword)

	ciphertext := restic.GetChunkBuf("BenchmarkDecrypt")
	defer restic.FreeChunkBuf("BenchmarkDecrypt", ciphertext)
	plaintext := restic.GetChunkBuf("BenchmarkDecrypt")
	defer restic.FreeChunkBuf("BenchmarkDecrypt", plaintext)

	n, err := k.Encrypt(ciphertext, data)
	ok(b, err)

	b.ResetTimer()
	b.SetBytes(int64(size))

	for i := 0; i < b.N; i++ {
		plaintext, err = k.Decrypt(plaintext, ciphertext[:n])
		ok(b, err)
	}
}

func TestEncryptStreamWriter(t *testing.T) {
	s := setupBackend(t)
	defer teardownBackend(t, s)
	k := setupKey(t, s, testPassword)

	tests := []int{5, 23, 2<<18 + 23, 1 << 20}
	if *testLargeCrypto {
		tests = append(tests, 7<<20+123)
	}

	for _, size := range tests {
		data := make([]byte, size)
		_, err := io.ReadFull(randomReader(42, size), data)
		ok(t, err)

		ciphertext := bytes.NewBuffer(nil)
		wr := k.EncryptTo(ciphertext)

		_, err = io.Copy(wr, bytes.NewReader(data))
		ok(t, err)
		ok(t, wr.Close())

		l := len(data) + restic.CiphertextExtension
		assert(t, len(ciphertext.Bytes()) == l,
			"wrong ciphertext length: expected %d, got %d",
			l, len(ciphertext.Bytes()))

		// decrypt with default function
		plaintext, err := k.Decrypt([]byte{}, ciphertext.Bytes())
		ok(t, err)
		assert(t, bytes.Equal(data, plaintext),
			"wrong plaintext after decryption: expected %02x, got %02x",
			data, plaintext)
	}
}

func TestDecryptStreamReader(t *testing.T) {
	s := setupBackend(t)
	defer teardownBackend(t, s)
	k := setupKey(t, s, testPassword)

	tests := []int{5, 23, 2<<18 + 23, 1 << 20}
	if *testLargeCrypto {
		tests = append(tests, 7<<20+123)
	}

	for _, size := range tests {
		data := make([]byte, size)
		_, err := io.ReadFull(randomReader(42, size), data)
		ok(t, err)

		ciphertext := make([]byte, size+restic.CiphertextExtension)

		// encrypt with default function
		n, err := k.Encrypt(ciphertext, data)
		ok(t, err)
		assert(t, n == len(data)+restic.CiphertextExtension,
			"wrong number of bytes returned after encryption: expected %d, got %d",
			len(data)+restic.CiphertextExtension, n)

		rd, err := k.DecryptFrom(bytes.NewReader(ciphertext))
		ok(t, err)

		plaintext, err := ioutil.ReadAll(rd)
		ok(t, err)

		assert(t, bytes.Equal(data, plaintext),
			"wrong plaintext after decryption: expected %02x, got %02x",
			data, plaintext)
	}
}

func TestEncryptWriter(t *testing.T) {
	s := setupBackend(t)
	defer teardownBackend(t, s)
	k := setupKey(t, s, testPassword)

	tests := []int{5, 23, 2<<18 + 23, 1 << 20}
	if *testLargeCrypto {
		tests = append(tests, 7<<20+123)
	}

	for _, size := range tests {
		data := make([]byte, size)
		_, err := io.ReadFull(randomReader(42, size), data)
		ok(t, err)

		buf := bytes.NewBuffer(nil)
		wr := k.EncryptTo(buf)

		_, err = io.Copy(wr, bytes.NewReader(data))
		ok(t, err)
		ok(t, wr.Close())

		ciphertext := buf.Bytes()

		l := len(data) + restic.CiphertextExtension
		assert(t, len(ciphertext) == l,
			"wrong ciphertext length: expected %d, got %d",
			l, len(ciphertext))

		// decrypt with default function
		plaintext, err := k.Decrypt([]byte{}, ciphertext)
		ok(t, err)
		assert(t, bytes.Equal(data, plaintext),
			"wrong plaintext after decryption: expected %02x, got %02x",
			data, plaintext)
	}
}
<|endoftext|>


<commit_before>// Derived from stdlib package text/template/parse.

// 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.

// das source lexer and parser.
package parse

import (
	"os"
	"fmt"
	"runtime"
	"strings"
	"strconv"
)

// Tree is the representation of a single parsed script.
type Tree struct {
	Name      string    // name of the script represented by the tree.
	Root      Node // top-level root of the tree.
	Ctx       Context
	text      string    // text parsed to create the script (or its parent)
	tab       bool
	// Parsing only; cleared after parse.
	lex       *Lexer
	token     [3]Item // three-token lookahead for parser.
	peekCount int
}

// Parse is shorthand for a New + *Tree.Parse combo.
func Parse(name, text string, tab bool) (t *Tree, err error) {
	return New(name).Parse(text, tab)
}

// next returns the next token.
func (t *Tree) next() Item {
	if t.peekCount > 0 {
		t.peekCount--
	} else {
		t.token[0] = t.lex.NextItem()
	}
	return t.token[t.peekCount]
}

// backup backs the input stream up one token.
func (t *Tree) backup() {
	t.peekCount++
}

// backup2 backs the input stream up two tokens.
// The zeroth token is already there.
func (t *Tree) backup2(t1 Item) {
	t.token[1] = t1
	t.peekCount = 2
}

// backup3 backs the input stream up three tokens
// The zeroth token is already there.
func (t *Tree) backup3(t2, t1 Item) { // Reverse order: we're pushing back.
	t.token[1] = t1
	t.token[2] = t2
	t.peekCount = 3
}

// peek returns but does not consume the next token.
func (t *Tree) peek() Item {
	if t.peekCount > 0 {
		return t.token[t.peekCount-1]
	}
	t.peekCount = 1
	t.token[0] = t.lex.NextItem()
	return t.token[0]
}

// nextNonSpace returns the next non-space token.
func (t *Tree) nextNonSpace() (token Item) {
	for {
		token = t.next()
		if token.Typ != ItemSpace {
			break
		}
	}
	return token
}

// peekNonSpace returns but does not consume the next non-space token.
func (t *Tree) peekNonSpace() (token Item) {
	for {
		token = t.next()
		if token.Typ != ItemSpace {
			break
		}
	}
	t.backup()
	return token
}

// Parsing.

// New allocates a new parse tree with the given name.
func New(name string) *Tree {
	return &Tree{
		Name:  name,
	}
}

// ErrorContext returns a textual representation of the location of the node in the input text.
func (t *Tree) ErrorContext(n Node) (location, context string) {
	pos := int(n.Position())
	text := t.text[:pos]
	byteNum := strings.LastIndex(text, "\n")
	if byteNum == -1 {
		byteNum = pos // On first line.
	} else {
		byteNum++ // After the newline.
		byteNum = pos - byteNum
	}
	lineNum := 1 + strings.Count(text, "\n")
	context = n.String()
	if len(context) > 20 {
		context = fmt.Sprintf("%.20s...", context)
	}
	return fmt.Sprintf("%s:%d:%d", t.Name, lineNum, byteNum), context
}

// errorf formats the error and terminates processing.
func (t *Tree) errorf(format string, args ...interface{}) {
	t.Root = nil
	format = fmt.Sprintf("das: %s:%d: %s", t.Name, t.lex.lineNumber(), format)
	panic(fmt.Errorf(format, args...))
}

// error terminates processing.
func (t *Tree) error(err error) {
	t.errorf("%s", err)
}

// expect consumes the next token and guarantees it has the required type.
func (t *Tree) expect(expected ItemType, context string) Item {
	token := t.nextNonSpace()
	if token.Typ != expected {
		t.unexpected(token, context)
	}
	return token
}

// expectOneOf consumes the next token and guarantees it has one of the required types.
func (t *Tree) expectOneOf(expected1, expected2 ItemType, context string) Item {
	token := t.nextNonSpace()
	if token.Typ != expected1 && token.Typ != expected2 {
		t.unexpected(token, context)
	}
	return token
}

// unexpected complains about the token and terminates processing.
func (t *Tree) unexpected(token Item, context string) {
	t.errorf("unexpected %s in %s", token, context)
}

// recover is the handler that turns panics into returns from the top level of Parse.
func (t *Tree) recover(errp *error) {
	e := recover()
	if e != nil {
		if _, ok := e.(runtime.Error); ok {
			panic(e)
		}
		if t != nil {
			t.stopParse()
		}
		*errp = e.(error)
	}
	return
}

// stopParse terminates parsing.
func (t *Tree) stopParse() {
	t.lex = nil
	if t.tab {
		t.Root = nil
	} else {
		t.Ctx = nil
	}
}

// Parse parses the script to construct a representation of the script for
// execution.
func (t *Tree) Parse(text string, tab bool) (tree *Tree, err error) {
	defer t.recover(&err)

	t.text = text
	t.tab = tab
	t.lex = Lex(t.Name, text)
	t.peekCount = 0

	// TODO This now only parses a pipeline.
	t.Root = t.pipeline()

	t.stopParse()
	return t, nil
}

func (t *Tree) pipeline() *ListNode {
	pipe := newList(t.peek().Pos)
loop:
	for {
		n := t.command()
		pipe.append(n)

		switch token := t.next(); token.Typ {
		case ItemPipe:
			continue loop
		case ItemEndOfLine, ItemEOF:
			break loop
		default:
			t.unexpected(token, "end of pipeline")
		}
	}
	return pipe
}

// command parses a command.
func (t *Tree) command() *CommandNode {
	cmd := newCommand(t.peek().Pos)
loop:
	for {
		switch t.peekNonSpace().Typ {
		case ItemBare, ItemSingleQuoted, ItemDoubleQuoted:
			cmd.append(t.term())
		case ItemRedirLeader:
			cmd.Redirs = append(cmd.Redirs, t.redir())
		default:
			break loop
		}
	}
	return cmd
}

func unquote(token Item) (string, error) {
	switch token.Typ {
	case ItemBare:
		return token.Val, nil
	case ItemSingleQuoted:
		return strings.Replace(token.Val[1:len(token.Val)-1], "''", "'", -1),
		       nil
	case ItemDoubleQuoted:
		return strconv.Unquote(token.Val)
	default:
		return "", fmt.Errorf("Can't unquote token: %v", token)
	}
}

func (t *Tree) term() Node {
	switch token := t.next(); token.Typ {
	case ItemBare, ItemSingleQuoted, ItemDoubleQuoted:
		text, err := unquote(token)
		if err != nil {
			t.error(err)
		}
		if token.End & MayContinue != 0 {
			t.Ctx = NewArgContext(token.Val)
		} else {
			t.Ctx = nil
		}
		return newString(token.Pos, token.Val, text)
	default:
		t.unexpected(token, "term")
		return nil
	}
}

// redir parses an IO redirection.
func (t *Tree) redir() Redir {
	leader := t.next()

	// Partition the redirection leader into direction and qualifier parts.
	// For example, if leader.Val == ">>[1=2]", dir == ">>" and qual == "1=2".
	var dir, qual string

	if i := strings.IndexRune(leader.Val, '['); i != -1 {
		dir = leader.Val[:i]
		qual = leader.Val[i+1:len(leader.Val)-1]
	} else {
		dir = leader.Val
	}

	// Determine the flag and default (new) fd from the direction.
	var (
		fd uintptr
		flag int
	)

	switch dir {
	case "<":
		flag = os.O_RDONLY
		fd = 0
	case "<>":
		flag = os.O_RDWR | os.O_CREATE
		fd = 0
	case ">":
		flag = os.O_WRONLY | os.O_CREATE
		fd = 1
	case ">>":
		flag = os.O_WRONLY | os.O_CREATE | os.O_APPEND
		fd = 1
	default:
		t.errorf("Unexpected redirection direction %q", dir)
	}

	if len(qual) > 0 {
		// Qualified redirection
		if i := strings.IndexRune(qual, '='); i != -1 {
			// FdRedir or CloseRedir
			lhs := qual[:i]
			rhs := qual[i+1:]
			if len(lhs) > 0 {
				var err error
				fd, err = Atou(lhs)
				if err != nil {
					t.errorf("Invalid new fd in qualified redirection %q", lhs)
				}
			}
			if len(rhs) > 0 {
				oldfd, err := Atou(rhs)
				if err != nil {
					t.errorf("Invalid old fd in qualified redirection %q", rhs)
				}
				return NewFdRedir(fd, oldfd)
			} else {
				return newCloseRedir(fd)
			}
		} else {
			// FilenameRedir with fd altered
			var err error
			fd, err = Atou(qual)
			if err != nil {
				t.errorf("Invalid new fd in qualified redirection %q", qual)
			}
		}
	}
	// FilenameRedir
	t.peekNonSpace()
	return newFilenameRedir(fd, flag, t.term())
}
<commit_msg>parse: Document the grammar<commit_after>// Derived from stdlib package text/template/parse.

// 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.

// das source lexer and parser.
package parse

import (
	"os"
	"fmt"
	"runtime"
	"strings"
	"strconv"
)

// Tree is the representation of a single parsed script.
type Tree struct {
	Name      string    // name of the script represented by the tree.
	Root      Node // top-level root of the tree.
	Ctx       Context
	text      string    // text parsed to create the script (or its parent)
	tab       bool
	// Parsing only; cleared after parse.
	lex       *Lexer
	token     [3]Item // three-token lookahead for parser.
	peekCount int
}

// Parse is shorthand for a New + *Tree.Parse combo.
func Parse(name, text string, tab bool) (t *Tree, err error) {
	return New(name).Parse(text, tab)
}

// next returns the next token.
func (t *Tree) next() Item {
	if t.peekCount > 0 {
		t.peekCount--
	} else {
		t.token[0] = t.lex.NextItem()
	}
	return t.token[t.peekCount]
}

// backup backs the input stream up one token.
func (t *Tree) backup() {
	t.peekCount++
}

// backup2 backs the input stream up two tokens.
// The zeroth token is already there.
func (t *Tree) backup2(t1 Item) {
	t.token[1] = t1
	t.peekCount = 2
}

// backup3 backs the input stream up three tokens
// The zeroth token is already there.
func (t *Tree) backup3(t2, t1 Item) { // Reverse order: we're pushing back.
	t.token[1] = t1
	t.token[2] = t2
	t.peekCount = 3
}

// peek returns but does not consume the next token.
func (t *Tree) peek() Item {
	if t.peekCount > 0 {
		return t.token[t.peekCount-1]
	}
	t.peekCount = 1
	t.token[0] = t.lex.NextItem()
	return t.token[0]
}

// nextNonSpace returns the next non-space token.
func (t *Tree) nextNonSpace() (token Item) {
	for {
		token = t.next()
		if token.Typ != ItemSpace {
			break
		}
	}
	return token
}

// peekNonSpace returns but does not consume the next non-space token.
func (t *Tree) peekNonSpace() (token Item) {
	for {
		token = t.next()
		if token.Typ != ItemSpace {
			break
		}
	}
	t.backup()
	return token
}

// Parsing.

// New allocates a new parse tree with the given name.
func New(name string) *Tree {
	return &Tree{
		Name:  name,
	}
}

// ErrorContext returns a textual representation of the location of the node in the input text.
func (t *Tree) ErrorContext(n Node) (location, context string) {
	pos := int(n.Position())
	text := t.text[:pos]
	byteNum := strings.LastIndex(text, "\n")
	if byteNum == -1 {
		byteNum = pos // On first line.
	} else {
		byteNum++ // After the newline.
		byteNum = pos - byteNum
	}
	lineNum := 1 + strings.Count(text, "\n")
	context = n.String()
	if len(context) > 20 {
		context = fmt.Sprintf("%.20s...", context)
	}
	return fmt.Sprintf("%s:%d:%d", t.Name, lineNum, byteNum), context
}

// errorf formats the error and terminates processing.
func (t *Tree) errorf(format string, args ...interface{}) {
	t.Root = nil
	format = fmt.Sprintf("das: %s:%d: %s", t.Name, t.lex.lineNumber(), format)
	panic(fmt.Errorf(format, args...))
}

// error terminates processing.
func (t *Tree) error(err error) {
	t.errorf("%s", err)
}

// expect consumes the next token and guarantees it has the required type.
func (t *Tree) expect(expected ItemType, context string) Item {
	token := t.nextNonSpace()
	if token.Typ != expected {
		t.unexpected(token, context)
	}
	return token
}

// expectOneOf consumes the next token and guarantees it has one of the required types.
func (t *Tree) expectOneOf(expected1, expected2 ItemType, context string) Item {
	token := t.nextNonSpace()
	if token.Typ != expected1 && token.Typ != expected2 {
		t.unexpected(token, context)
	}
	return token
}

// unexpected complains about the token and terminates processing.
func (t *Tree) unexpected(token Item, context string) {
	t.errorf("unexpected %s in %s", token, context)
}

// recover is the handler that turns panics into returns from the top level of Parse.
func (t *Tree) recover(errp *error) {
	e := recover()
	if e != nil {
		if _, ok := e.(runtime.Error); ok {
			panic(e)
		}
		if t != nil {
			t.stopParse()
		}
		*errp = e.(error)
	}
	return
}

// stopParse terminates parsing.
func (t *Tree) stopParse() {
	t.lex = nil
	if t.tab {
		t.Root = nil
	} else {
		t.Ctx = nil
	}
}

// Parse parses the script to construct a representation of the script for
// execution.
func (t *Tree) Parse(text string, tab bool) (tree *Tree, err error) {
	defer t.recover(&err)

	t.text = text
	t.tab = tab
	t.lex = Lex(t.Name, text)
	t.peekCount = 0

	// TODO This now only parses a pipeline.
	t.Root = t.pipeline()

	t.stopParse()
	return t, nil
}

// Pipeline = Command { "|" Command }
func (t *Tree) pipeline() *ListNode {
	pipe := newList(t.peek().Pos)
loop:
	for {
		n := t.command()
		pipe.append(n)

		switch token := t.next(); token.Typ {
		case ItemPipe:
			continue loop
		case ItemEndOfLine, ItemEOF:
			break loop
		default:
			t.unexpected(token, "end of pipeline")
		}
	}
	return pipe
}

// command parses a command.
// Command = [ space ] Term { [ space ] Term } { [ space ] Redir }
// XXX the parser is now more permissive than the described grammar.
func (t *Tree) command() *CommandNode {
	cmd := newCommand(t.peek().Pos)
loop:
	for {
		switch t.peekNonSpace().Typ {
		case ItemBare, ItemSingleQuoted, ItemDoubleQuoted:
			cmd.append(t.term())
		case ItemRedirLeader:
			cmd.Redirs = append(cmd.Redirs, t.redir())
		default:
			break loop
		}
	}
	return cmd
}

func unquote(token Item) (string, error) {
	switch token.Typ {
	case ItemBare:
		return token.Val, nil
	case ItemSingleQuoted:
		return strings.Replace(token.Val[1:len(token.Val)-1], "''", "'", -1),
		       nil
	case ItemDoubleQuoted:
		return strconv.Unquote(token.Val)
	default:
		return "", fmt.Errorf("Can't unquote token: %v", token)
	}
}

// Term = bare | single-quoted | double-quoted
func (t *Tree) term() Node {
	switch token := t.next(); token.Typ {
	case ItemBare, ItemSingleQuoted, ItemDoubleQuoted:
		text, err := unquote(token)
		if err != nil {
			t.error(err)
		}
		if token.End & MayContinue != 0 {
			t.Ctx = NewArgContext(token.Val)
		} else {
			t.Ctx = nil
		}
		return newString(token.Pos, token.Val, text)
	default:
		t.unexpected(token, "term")
		return nil
	}
}

// redir parses an IO redirection.
// Redir = redir-leader [ [ space ] Term ]
// NOTE The actual grammar is more complex than above, since 1) the inner
// structure of redir-leader is also parsed here, and 2) the Term is not truly
// optional, but sometimes required depending on the redir-leader.
func (t *Tree) redir() Redir {
	leader := t.next()

	// Partition the redirection leader into direction and qualifier parts.
	// For example, if leader.Val == ">>[1=2]", dir == ">>" and qual == "1=2".
	var dir, qual string

	if i := strings.IndexRune(leader.Val, '['); i != -1 {
		dir = leader.Val[:i]
		qual = leader.Val[i+1:len(leader.Val)-1]
	} else {
		dir = leader.Val
	}

	// Determine the flag and default (new) fd from the direction.
	var (
		fd uintptr
		flag int
	)

	switch dir {
	case "<":
		flag = os.O_RDONLY
		fd = 0
	case "<>":
		flag = os.O_RDWR | os.O_CREATE
		fd = 0
	case ">":
		flag = os.O_WRONLY | os.O_CREATE
		fd = 1
	case ">>":
		flag = os.O_WRONLY | os.O_CREATE | os.O_APPEND
		fd = 1
	default:
		t.errorf("Unexpected redirection direction %q", dir)
	}

	if len(qual) > 0 {
		// Qualified redirection
		if i := strings.IndexRune(qual, '='); i != -1 {
			// FdRedir or CloseRedir
			lhs := qual[:i]
			rhs := qual[i+1:]
			if len(lhs) > 0 {
				var err error
				fd, err = Atou(lhs)
				if err != nil {
					t.errorf("Invalid new fd in qualified redirection %q", lhs)
				}
			}
			if len(rhs) > 0 {
				oldfd, err := Atou(rhs)
				if err != nil {
					t.errorf("Invalid old fd in qualified redirection %q", rhs)
				}
				return NewFdRedir(fd, oldfd)
			} else {
				return newCloseRedir(fd)
			}
		} else {
			// FilenameRedir with fd altered
			var err error
			fd, err = Atou(qual)
			if err != nil {
				t.errorf("Invalid new fd in qualified redirection %q", qual)
			}
		}
	}
	// FilenameRedir
	t.peekNonSpace()
	return newFilenameRedir(fd, flag, t.term())
}
<|endoftext|>


<commit_before>package veneur

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stripe/veneur/samplers"
)

func TestParser(t *testing.T) {
	m, _ := samplers.ParseMetric([]byte("a.b.c:1|c"))
	assert.NotNil(t, m, "Got nil metric!")
	assert.Equal(t, "a.b.c", m.Name, "Name")
	assert.Equal(t, float64(1), m.Value, "Value")
	assert.Equal(t, "counter", m.Type, "Type")
}

func TestParserGauge(t *testing.T) {
	m, _ := samplers.ParseMetric([]byte("a.b.c:1|g"))
	assert.NotNil(t, m, "Got nil metric!")
	assert.Equal(t, "a.b.c", m.Name, "Name")
	assert.Equal(t, float64(1), m.Value, "Value")
	assert.Equal(t, "gauge", m.Type, "Type")
}

func TestParserHistogram(t *testing.T) {
	m, _ := samplers.ParseMetric([]byte("a.b.c:1|h"))
	assert.NotNil(t, m, "Got nil metric!")
	assert.Equal(t, "a.b.c", m.Name, "Name")
	assert.Equal(t, float64(1), m.Value, "Value")
	assert.Equal(t, "histogram", m.Type, "Type")
}

func TestParserTimer(t *testing.T) {
	m, _ := samplers.ParseMetric([]byte("a.b.c:1|ms"))
	assert.NotNil(t, m, "Got nil metric!")
	assert.Equal(t, "a.b.c", m.Name, "Name")
	assert.Equal(t, float64(1), m.Value, "Value")
	assert.Equal(t, "timer", m.Type, "Type")
}

func TestParserSet(t *testing.T) {
	m, _ := samplers.ParseMetric([]byte("a.b.c:foo|s"))
	assert.NotNil(t, m, "Got nil metric!")
	assert.Equal(t, "a.b.c", m.Name, "Name")
	assert.Equal(t, "foo", m.Value, "Value")
	assert.Equal(t, "set", m.Type, "Type")
}

func TestParserWithTags(t *testing.T) {
	m, _ := samplers.ParseMetric([]byte("a.b.c:1|c|#foo:bar,baz:gorch"))
	assert.NotNil(t, m, "Got nil metric!")
	assert.Equal(t, "a.b.c", m.Name, "Name")
	assert.Equal(t, float64(1), m.Value, "Value")
	assert.Equal(t, "counter", m.Type, "Type")
	assert.Equal(t, 2, len(m.Tags), "# of Tags")

	_, valueError := samplers.ParseMetric([]byte("a.b.c:fart|c"))
	assert.NotNil(t, valueError, "No errors when parsing")
	assert.Contains(t, valueError.Error(), "Invalid number", "Invalid number error missing")
}

func TestParserWithSampleRate(t *testing.T) {
	m, _ := samplers.ParseMetric([]byte("a.b.c:1|c|@0.1"))
	assert.NotNil(t, m, "Got nil metric!")
	assert.Equal(t, "a.b.c", m.Name, "Name")
	assert.Equal(t, float64(1), m.Value, "Value")
	assert.Equal(t, "counter", m.Type, "Type")
	assert.Equal(t, float32(0.1), m.SampleRate, "Sample Rate")

	_, valueError := samplers.ParseMetric([]byte("a.b.c:fart|c"))
	assert.NotNil(t, valueError, "No errors when parsing")
	assert.Contains(t, valueError.Error(), "Invalid number", "Invalid number error missing")

	_, valueError = samplers.ParseMetric([]byte("a.b.c:1|g|@0.1"))
	assert.NoError(t, valueError, "Got errors when parsing")
}

func TestParserWithSampleRateAndTags(t *testing.T) {
	m, _ := samplers.ParseMetric([]byte("a.b.c:1|c|@0.1|#foo:bar,baz:gorch"))
	assert.NotNil(t, m, "Got nil metric!")
	assert.Equal(t, "a.b.c", m.Name, "Name")
	assert.Equal(t, float64(1), m.Value, "Value")
	assert.Equal(t, "counter", m.Type, "Type")
	assert.Equal(t, float32(0.1), m.SampleRate, "Sample Rate")
	assert.Len(t, m.Tags, 2, "Tags")

	_, valueError := samplers.ParseMetric([]byte("a.b.c:fart|c"))
	assert.NotNil(t, valueError, "No errors when parsing")
	assert.Contains(t, valueError.Error(), "Invalid number", "Invalid number error missing")
}

func TestInvalidPackets(t *testing.T) {
	table := map[string]string{
		"foo":                                "1 colon",
		"foo:1":                              "1 pipe",
		"foo:1||":                            "metric type",
		"foo:|c|":                            "metric value",
		"this_is_a_bad_metric:nan|g|#shell":  "metric value",
		"this_is_a_bad_metric:NaN|g|#shell":  "metric value",
		"this_is_a_bad_metric:-inf|g|#shell": "metric value",
		"this_is_a_bad_metric:+inf|g|#shell": "metric value",
		"foo:1|foo|":                         "Invalid type",
		"foo:1|c||":                          "pipes",
		"foo:1|c|foo":                        "unknown section",
		"foo:1|c|@-0.1":                      ">0",
		"foo:1|c|@1.1":                       "<=1",
		"foo:1|c|@0.5|@0.2":                  "multiple sample rates",
		"foo:1|c|#foo|#bar":                  "multiple tag sections",
	}

	for packet, errContent := range table {
		_, err := samplers.ParseMetric([]byte(packet))
		assert.NotNil(t, err, "Should have gotten error parsing %q", packet)
		assert.Contains(t, err.Error(), errContent, "Error should have contained text")
	}
}

func TestLocalOnlyEscape(t *testing.T) {
	m, err := samplers.ParseMetric([]byte("a.b.c:1|h|#veneurlocalonly,tag2:quacks"))
	assert.NoError(t, err, "should have no error parsing")
	assert.Equal(t, samplers.LocalOnly, m.Scope, "should have gotten local only metric")
	assert.NotContains(t, m.Tags, "veneurlocalonly", "veneurlocalonly should not actually be a tag")
	assert.Contains(t, m.Tags, "tag2:quacks", "tag2 should be preserved in the list of tags after removing magic tags")
}

func TestGlobalOnlyEscape(t *testing.T) {
	m, err := samplers.ParseMetric([]byte("a.b.c:1|h|#veneurglobalonly,tag2:quacks"))
	assert.NoError(t, err, "should have no error parsing")
	assert.Equal(t, samplers.GlobalOnly, m.Scope, "should have gotten local only metric")
	assert.NotContains(t, m.Tags, "veneurglobalonly", "veneurlocalonly should not actually be a tag")
	assert.Contains(t, m.Tags, "tag2:quacks", "tag2 should be preserved in the list of tags after removing magic tags")
}

func TestEvents(t *testing.T) {
	evt, err := samplers.ParseEvent([]byte("_e{3,3}:foo|bar|k:foos|s:test|t:success|p:low|#foo:bar,baz:qux|d:1136239445|h:example.com"))
	assert.NoError(t, err, "should have parsed correctly")
	assert.EqualValues(t, &samplers.UDPEvent{
		Title:       "foo",
		Text:        "bar",
		Timestamp:   1136239445,
		Aggregation: "foos",
		Source:      "test",
		AlertLevel:  "success",
		Priority:    "low",
		Tags:        []string{"foo:bar", "baz:qux"},
		Hostname:    "example.com",
	}, evt, "should have parsed event")

	table := map[string]string{
		"_e{4,3}:foo|bar":               "title length",
		"_e{3,4}:foo|bar":               "text length",
		"_e{3,3}:foo|bar|d:abc":         "date",
		"_e{3,3}:foo|bar|p:baz":         "priority",
		"_e{3,3}:foo|bar|t:baz":         "alert",
		"_e{3,3}:foo|bar|t:info|t:info": "multiple alert",
		"_e{3,3}:foo|bar||":             "pipe",
		"_e{3,0}:foo||":                 "text length",
		"_e{3,3}:foo":                   "text",
		"_e{3,3}":                       "colon",
	}
	for packet, errContent := range table {
		_, err := samplers.ParseEvent([]byte(packet))
		assert.NotNil(t, err, "Should have gotten error parsing %q", packet)
		assert.Contains(t, err.Error(), errContent, "Error should have contained text")
	}
}

func TestServiceChecks(t *testing.T) {
	evt, err := samplers.ParseServiceCheck([]byte("_sc|foo.bar|0|d:1136239445|h:example.com"))
	assert.NoError(t, err, "should have parsed correctly")
	assert.EqualValues(t, &samplers.UDPServiceCheck{
		Name:      "foo.bar",
		Status:    0,
		Timestamp: 1136239445,
		Hostname:  "example.com",
	}, evt, "should have parsed event")

	table := map[string]string{
		"foo.bar|0":           "_sc",
		"_sc|foo.bar":         "status",
		"_sc|foo.bar|5":       "status",
		"_sc|foo.bar|0||":     "pipe",
		"_sc|foo.bar|0|d:abc": "date",
	}
	for packet, errContent := range table {
		_, err := samplers.ParseServiceCheck([]byte(packet))
		assert.NotNil(t, err, "Should have gotten error parsing %q", packet)
		assert.Contains(t, err.Error(), errContent, "Error should have contained text")
	}
}

func TestEventMessageUnescape(t *testing.T) {
	packet := []byte("_e{3,15}:foo|foo\\nbar\\nbaz\\n")
	evt, err := samplers.ParseEvent(packet)
	assert.NoError(t, err, "Should have parsed correctly")
	assert.Equal(t, "foo\nbar\nbaz\n", evt.Text, "Should contain newline")
}

func TestServiceCheckMessageUnescape(t *testing.T) {
	packet := []byte("_sc|foo|0|m:foo\\nbar\\nbaz\\n")
	svcheck, err := samplers.ParseServiceCheck(packet)
	assert.NoError(t, err, "Should have parsed correctly")
	assert.Equal(t, "foo\nbar\nbaz\n", svcheck.Message, "Should contain newline")
}
<commit_msg>Add tests for SSF parsing<commit_after>package veneur

import (
	"testing"

	"fmt"
	"github.com/stretchr/testify/assert"
	"github.com/stripe/veneur/samplers"
	"github.com/stripe/veneur/ssf"
)

func freshSSFMetric() *ssf.SSFSample {
	return &ssf.SSFSample{
		Metric:     0,
		Name:       "test.ssf_metric",
		Value:      5,
		Message:    "test_msg",
		Status:     0,
		SampleRate: 1,
		Tags: map[string]string{
			"tag1": "value1",
			"tag2": "value2",
		},
	}
}

func TestParserSSF(t *testing.T) {
	standardMetric := freshSSFMetric()
	m, _ := samplers.ParseMetricSSF(standardMetric)
	fmt.Printf("%v\n", m)
	assert.NotNil(t, m, "Got nil metric!")
	assert.Equal(t, standardMetric.Name, m.Name, "Name")
	assert.Equal(t, float64(standardMetric.Value), m.Value, "Value")
	assert.Equal(t, "counter", m.Type, "Type")
}

func TestParserSSFGauge(t *testing.T) {
	standardMetric := freshSSFMetric()
	standardMetric.Metric = ssf.SSFSample_GAUGE
	m, _ := samplers.ParseMetricSSF(standardMetric)
	assert.NotNil(t, m, "Got nil metric!")
	assert.Equal(t, standardMetric.Name, m.Name, "Name")
	assert.Equal(t, float64(standardMetric.Value), m.Value, "Value")
	assert.Equal(t, "gauge", m.Type, "Type")
}

func TestParserSSFSet(t *testing.T) {
	standardMetric := freshSSFMetric()
	standardMetric.Metric = ssf.SSFSample_SET
	m, _ := samplers.ParseMetricSSF(standardMetric)
	assert.NotNil(t, m, "Got nil metric!")
	assert.Equal(t, standardMetric.Name, m.Name, "Name")
	assert.Equal(t, standardMetric.Message, m.Value, "Value")
	assert.Equal(t, "set", m.Type, "Type")
}

func TestParserSSFWithTags(t *testing.T) {
	standardMetric := freshSSFMetric()
	m, _ := samplers.ParseMetricSSF(standardMetric)
	assert.NotNil(t, m, "Got nil metric!")
	assert.Equal(t, standardMetric.Name, m.Name, "Name")
	assert.Equal(t, float64(standardMetric.Value), m.Value, "Value")
	assert.Equal(t, "counter", m.Type, "Type")
	assert.Equal(t, 2, len(m.Tags), "# of Tags")
}

func TestParserSSFWithSampleRate(t *testing.T) {
	standardMetric := freshSSFMetric()
	standardMetric.SampleRate = 0.1
	m, _ := samplers.ParseMetricSSF(standardMetric)
	assert.NotNil(t, m, "Got nil metric!")
	assert.Equal(t, standardMetric.Name, m.Name, "Name")
	assert.Equal(t, float64(standardMetric.Value), m.Value, "Value")
	assert.Equal(t, "counter", m.Type, "Type")
	assert.Equal(t, float32(0.1), m.SampleRate, "Sample Rate")
}

func TestParserSSFWithSampleRateAndTags(t *testing.T) {
	standardMetric := freshSSFMetric()
	standardMetric.SampleRate = 0.1
	m, _ := samplers.ParseMetricSSF(standardMetric)
	assert.NotNil(t, m, "Got nil metric!")
	assert.Equal(t, standardMetric.Name, m.Name, "Name")
	assert.Equal(t, float64(standardMetric.Value), m.Value, "Value")
	assert.Equal(t, "counter", m.Type, "Type")
	assert.Equal(t, float32(0.1), m.SampleRate, "Sample Rate")
	assert.Len(t, m.Tags, 2, "Tags")
}

func TestParser(t *testing.T) {
	m, _ := samplers.ParseMetric([]byte("a.b.c:1|c"))
	assert.NotNil(t, m, "Got nil metric!")
	assert.Equal(t, "a.b.c", m.Name, "Name")
	assert.Equal(t, float64(1), m.Value, "Value")
	assert.Equal(t, "counter", m.Type, "Type")
}

func TestParserGauge(t *testing.T) {
	m, _ := samplers.ParseMetric([]byte("a.b.c:1|g"))
	assert.NotNil(t, m, "Got nil metric!")
	assert.Equal(t, "a.b.c", m.Name, "Name")
	assert.Equal(t, float64(1), m.Value, "Value")
	assert.Equal(t, "gauge", m.Type, "Type")
}

func TestParserHistogram(t *testing.T) {
	m, _ := samplers.ParseMetric([]byte("a.b.c:1|h"))
	assert.NotNil(t, m, "Got nil metric!")
	assert.Equal(t, "a.b.c", m.Name, "Name")
	assert.Equal(t, float64(1), m.Value, "Value")
	assert.Equal(t, "histogram", m.Type, "Type")
}

func TestParserTimer(t *testing.T) {
	m, _ := samplers.ParseMetric([]byte("a.b.c:1|ms"))
	assert.NotNil(t, m, "Got nil metric!")
	assert.Equal(t, "a.b.c", m.Name, "Name")
	assert.Equal(t, float64(1), m.Value, "Value")
	assert.Equal(t, "timer", m.Type, "Type")
}

func TestParserSet(t *testing.T) {
	m, _ := samplers.ParseMetric([]byte("a.b.c:foo|s"))
	assert.NotNil(t, m, "Got nil metric!")
	assert.Equal(t, "a.b.c", m.Name, "Name")
	assert.Equal(t, "foo", m.Value, "Value")
	assert.Equal(t, "set", m.Type, "Type")
}

func TestParserWithTags(t *testing.T) {
	m, _ := samplers.ParseMetric([]byte("a.b.c:1|c|#foo:bar,baz:gorch"))
	assert.NotNil(t, m, "Got nil metric!")
	assert.Equal(t, "a.b.c", m.Name, "Name")
	assert.Equal(t, float64(1), m.Value, "Value")
	assert.Equal(t, "counter", m.Type, "Type")
	assert.Equal(t, 2, len(m.Tags), "# of Tags")

	_, valueError := samplers.ParseMetric([]byte("a.b.c:fart|c"))
	assert.NotNil(t, valueError, "No errors when parsing")
	assert.Contains(t, valueError.Error(), "Invalid number", "Invalid number error missing")
}

func TestParserWithSampleRate(t *testing.T) {
	m, _ := samplers.ParseMetric([]byte("a.b.c:1|c|@0.1"))
	assert.NotNil(t, m, "Got nil metric!")
	assert.Equal(t, "a.b.c", m.Name, "Name")
	assert.Equal(t, float64(1), m.Value, "Value")
	assert.Equal(t, "counter", m.Type, "Type")
	assert.Equal(t, float32(0.1), m.SampleRate, "Sample Rate")

	_, valueError := samplers.ParseMetric([]byte("a.b.c:fart|c"))
	assert.NotNil(t, valueError, "No errors when parsing")
	assert.Contains(t, valueError.Error(), "Invalid number", "Invalid number error missing")

	_, valueError = samplers.ParseMetric([]byte("a.b.c:1|g|@0.1"))
	assert.NoError(t, valueError, "Got errors when parsing")
}

func TestParserWithSampleRateAndTags(t *testing.T) {
	m, _ := samplers.ParseMetric([]byte("a.b.c:1|c|@0.1|#foo:bar,baz:gorch"))
	assert.NotNil(t, m, "Got nil metric!")
	assert.Equal(t, "a.b.c", m.Name, "Name")
	assert.Equal(t, float64(1), m.Value, "Value")
	assert.Equal(t, "counter", m.Type, "Type")
	assert.Equal(t, float32(0.1), m.SampleRate, "Sample Rate")
	assert.Len(t, m.Tags, 2, "Tags")

	_, valueError := samplers.ParseMetric([]byte("a.b.c:fart|c"))
	assert.NotNil(t, valueError, "No errors when parsing")
	assert.Contains(t, valueError.Error(), "Invalid number", "Invalid number error missing")
}

func TestInvalidPackets(t *testing.T) {
	table := map[string]string{
		"foo":                                "1 colon",
		"foo:1":                              "1 pipe",
		"foo:1||":                            "metric type",
		"foo:|c|":                            "metric value",
		"this_is_a_bad_metric:nan|g|#shell":  "metric value",
		"this_is_a_bad_metric:NaN|g|#shell":  "metric value",
		"this_is_a_bad_metric:-inf|g|#shell": "metric value",
		"this_is_a_bad_metric:+inf|g|#shell": "metric value",
		"foo:1|foo|":                         "Invalid type",
		"foo:1|c||":                          "pipes",
		"foo:1|c|foo":                        "unknown section",
		"foo:1|c|@-0.1":                      ">0",
		"foo:1|c|@1.1":                       "<=1",
		"foo:1|c|@0.5|@0.2":                  "multiple sample rates",
		"foo:1|c|#foo|#bar":                  "multiple tag sections",
	}

	for packet, errContent := range table {
		_, err := samplers.ParseMetric([]byte(packet))
		assert.NotNil(t, err, "Should have gotten error parsing %q", packet)
		assert.Contains(t, err.Error(), errContent, "Error should have contained text")
	}
}

func TestLocalOnlyEscape(t *testing.T) {
	m, err := samplers.ParseMetric([]byte("a.b.c:1|h|#veneurlocalonly,tag2:quacks"))
	assert.NoError(t, err, "should have no error parsing")
	assert.Equal(t, samplers.LocalOnly, m.Scope, "should have gotten local only metric")
	assert.NotContains(t, m.Tags, "veneurlocalonly", "veneurlocalonly should not actually be a tag")
	assert.Contains(t, m.Tags, "tag2:quacks", "tag2 should be preserved in the list of tags after removing magic tags")
}

func TestGlobalOnlyEscape(t *testing.T) {
	m, err := samplers.ParseMetric([]byte("a.b.c:1|h|#veneurglobalonly,tag2:quacks"))
	assert.NoError(t, err, "should have no error parsing")
	assert.Equal(t, samplers.GlobalOnly, m.Scope, "should have gotten local only metric")
	assert.NotContains(t, m.Tags, "veneurglobalonly", "veneurlocalonly should not actually be a tag")
	assert.Contains(t, m.Tags, "tag2:quacks", "tag2 should be preserved in the list of tags after removing magic tags")
}

func TestEvents(t *testing.T) {
	evt, err := samplers.ParseEvent([]byte("_e{3,3}:foo|bar|k:foos|s:test|t:success|p:low|#foo:bar,baz:qux|d:1136239445|h:example.com"))
	assert.NoError(t, err, "should have parsed correctly")
	assert.EqualValues(t, &samplers.UDPEvent{
		Title:       "foo",
		Text:        "bar",
		Timestamp:   1136239445,
		Aggregation: "foos",
		Source:      "test",
		AlertLevel:  "success",
		Priority:    "low",
		Tags:        []string{"foo:bar", "baz:qux"},
		Hostname:    "example.com",
	}, evt, "should have parsed event")

	table := map[string]string{
		"_e{4,3}:foo|bar":               "title length",
		"_e{3,4}:foo|bar":               "text length",
		"_e{3,3}:foo|bar|d:abc":         "date",
		"_e{3,3}:foo|bar|p:baz":         "priority",
		"_e{3,3}:foo|bar|t:baz":         "alert",
		"_e{3,3}:foo|bar|t:info|t:info": "multiple alert",
		"_e{3,3}:foo|bar||":             "pipe",
		"_e{3,0}:foo||":                 "text length",
		"_e{3,3}:foo":                   "text",
		"_e{3,3}":                       "colon",
	}
	for packet, errContent := range table {
		_, err := samplers.ParseEvent([]byte(packet))
		assert.NotNil(t, err, "Should have gotten error parsing %q", packet)
		assert.Contains(t, err.Error(), errContent, "Error should have contained text")
	}
}

func TestServiceChecks(t *testing.T) {
	evt, err := samplers.ParseServiceCheck([]byte("_sc|foo.bar|0|d:1136239445|h:example.com"))
	assert.NoError(t, err, "should have parsed correctly")
	assert.EqualValues(t, &samplers.UDPServiceCheck{
		Name:      "foo.bar",
		Status:    0,
		Timestamp: 1136239445,
		Hostname:  "example.com",
	}, evt, "should have parsed event")

	table := map[string]string{
		"foo.bar|0":           "_sc",
		"_sc|foo.bar":         "status",
		"_sc|foo.bar|5":       "status",
		"_sc|foo.bar|0||":     "pipe",
		"_sc|foo.bar|0|d:abc": "date",
	}
	for packet, errContent := range table {
		_, err := samplers.ParseServiceCheck([]byte(packet))
		assert.NotNil(t, err, "Should have gotten error parsing %q", packet)
		assert.Contains(t, err.Error(), errContent, "Error should have contained text")
	}
}

func TestEventMessageUnescape(t *testing.T) {
	packet := []byte("_e{3,15}:foo|foo\\nbar\\nbaz\\n")
	evt, err := samplers.ParseEvent(packet)
	assert.NoError(t, err, "Should have parsed correctly")
	assert.Equal(t, "foo\nbar\nbaz\n", evt.Text, "Should contain newline")
}

func TestServiceCheckMessageUnescape(t *testing.T) {
	packet := []byte("_sc|foo|0|m:foo\\nbar\\nbaz\\n")
	svcheck, err := samplers.ParseServiceCheck(packet)
	assert.NoError(t, err, "Should have parsed correctly")
	assert.Equal(t, "foo\nbar\nbaz\n", svcheck.Message, "Should contain newline")
}
<|endoftext|>