<commit_before>package bleve

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"testing"

	"github.com/blevesearch/bleve"
	_ "github.com/blevesearch/bleve/analysis/analyzer/keyword"
	"github.com/ironman-project/ironman/template/model"
	"github.com/ironman-project/ironman/template/repository"
	uuid "github.com/satori/go.uuid"
)

func tempIndexPath(t *testing.T) string {
	dir, err := ioutil.TempDir("", "ironman-bleve-test")
	if err != nil {
		t.Fatal("Failed to create test bleve index directory", err)
	}

	indexPath := filepath.Join(dir, "index")
	return indexPath
}

func newTestRepository(t *testing.T) (repository.Repository, bleve.Index, func()) {
	path := tempIndexPath(t)

	index, err := BuildIndex(path)

	if err != nil {
		t.Fatalf("Failed to open test index %s", err)
	}

	r := New(SetIndex(index))

	return r, index, func() {
		err := index.Close()

		if err != nil {
			t.Fatal("Failed to close bleve index", err)
		}
		err = os.RemoveAll(path)
		if err != nil {
			t.Fatal("Failed to clean bleve index", err)
		}
	}
}

func Test_bleeveRepository_Index(t *testing.T) {

	type args struct {
		template *model.Template
	}
	tests := []struct {
		name    string
		args    args
		wantErr bool
	}{
		{
			"Index a template",
			args{
				&model.Template{
					ID:          "test-template",
					Name:        "Test template",
					Description: "This is a test template",
					Generators: []*model.Generator{
						&model.Generator{
							ID:          "test-generator",
							Name:        "Test generator",
							Description: "This is a test generator",
						},
					},
				},
			},
			false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			r, index, clean := newTestRepository(t)
			defer clean()
			var id string
			var err error
			if id, err = r.Index(tt.args.template); (err != nil) != tt.wantErr {
				t.Errorf("bleeveRepository.Index() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			doc, err := index.Document(id)

			if err != nil {
				t.Errorf("bleeveRepository.Index() error = %v, wantErr %v", err, tt.wantErr)
			}

			if doc == nil {
				t.Errorf("bleeveRepository.Index() nil , want %v", tt.args.template)
			}

			got, err := deserialize(doc)

			if err != nil {
				t.Errorf("bleeveRepository.Index()  error = %v", err)
			}

			if got != nil && !reflect.DeepEqual(got, tt.args.template) {
				t.Errorf("bleeveRepository.FindTemplateByID() = %v, want %v", got, tt.args.template)
			}

		})
	}
}

func Test_bleeveRepository_Update(t *testing.T) {

	type args struct {
		template *model.Template
	}
	tests := []struct {
		name     string
		args     args
		template *model.Template
		wantErr  bool
	}{
		{
			"Update template index",
			args{
				&model.Template{
					ID:          "template-id",
					Name:        "Updated name",
					Description: "Updated description",
					Generators: []*model.Generator{
						&model.Generator{
							ID:          "test-generator",
							Name:        "Test generator",
							Description: "This is a test generator",
						},
						&model.Generator{
							ID:          "test-generator2",
							Name:        "Test generator2",
							Description: "This is a test generator 2",
						},
					},
				},
			},
			&model.Template{
				ID: "template-id",
			},
			false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			r, index, clean := newTestRepository(t)
			defer clean()
			id := uuid.NewV4().String()
			tt.template.IID = id
			err := index.Index(id, tt.template)

			if err != nil {
				t.Error("Failed to index template to update", err)
			}

			if err := r.Update(tt.args.template); (err != nil) != tt.wantErr {
				t.Errorf("bleeveRepository.Update() error = %v, wantErr %v", err, tt.wantErr)
			}

			doc, err := index.Document(id)

			if doc == nil {
				t.Error("failed to retreive indexed document with ID", id)
			}

			if err != nil {
				t.Error("failed to retreive indexed document", tt.template, err)
			}

			for _, field := range doc.Fields {

				value := string(field.Value())
				switch field.Name() {
				case "IID":
					if string(value) == "" || (value != id) {
						t.Errorf("bleveRepository.Update() IID = %v want %v", value, id)
					}
				case "ID":
					if string(value) == "" || (value != tt.args.template.ID) {
						t.Errorf("bleveRepository.Update() templateID = %v want %v", value, tt.args.template.ID)
					}
				case "Name":
					if string(value) == "" || (value != tt.args.template.Name) {
						t.Errorf("bleveRepository.Update() templateName = %v want %v", value, tt.args.template.Name)
					}
				case "Description":
					if string(value) == "" || (value != tt.args.template.Description) {
						t.Errorf("bleveRepository.Update() templateDescription = %v want %v", value, tt.args.template.Description)
					}
				case "Generators.ID":
					pos := field.ArrayPositions()[0]
					expectedID := tt.args.template.Generators[pos].ID
					if value != expectedID {
						t.Errorf("bleveRepository.Update() template.Generators[%d].ID = %v want %v", pos, value, expectedID)
					}
				case "Generators.Name":
					pos := field.ArrayPositions()[0]
					expectedName := tt.args.template.Generators[pos].Name
					if value != expectedName {
						t.Errorf("bleveRepository.Update() template.Generators[%d].Name = %v want %v", pos, value, expectedName)
					}
				case "Generators.Description":
					pos := field.ArrayPositions()[0]
					expectedDescription := tt.args.template.Generators[pos].Description
					if value != expectedDescription {
						t.Errorf("bleveRepository.Update() template.Generators[%d].Description = %v want %v", pos, value, expectedDescription)
					}
				default:
					t.Error("doc.Fields should assert field", field.Name(), string(field.Value()))
				}
			}

		})
	}
}

func Test_bleeveRepository_FindTemplateByID(t *testing.T) {

	type args struct {
		ID string
	}
	tests := []struct {
		name    string
		args    args
		want    *model.Template
		wantErr bool
	}{
		{
			"Find by id",
			args{
				"test-template-id",
			},
			&model.Template{
				ID:          "test-template-id",
				Description: "Some description",
			},
			false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			r, index, clean := newTestRepository(t)
			defer clean()
			err := index.Index(uuid.NewV4().String(), tt.want)

			if err != nil {
				t.Errorf("Failed to index template model")
			}

			got, err := r.FindTemplateByID(tt.args.ID)

			if (err != nil) != tt.wantErr {
				t.Errorf("bleeveRepository.FindTemplateByID() error = %v, wantErr %v", err, tt.wantErr)
				return
			}

			if got != nil && !reflect.DeepEqual(got, tt.want) {
				t.Errorf("bleeveRepository.FindTemplateByID() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_bleeveRepository_Delete(t *testing.T) {

	type args struct {
		ID string
	}
	tests := []struct {
		name    string
		args    args
		want    bool
		wantErr bool
	}{
		{
			"Delete template from index",
			args{
				"template-id",
			},
			true,
			false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			r, index, clean := newTestRepository(t)
			defer clean()
			id := uuid.NewV4().String()
			templ := &model.Template{
				IID: id,
				ID:  "template-id",
			}
			err := index.Index(id, templ)

			if err != nil {
				t.Error("Failed to index template to update", err)
			}
			got, err := r.Delete(tt.args.ID)
			if (err != nil) != tt.wantErr {
				t.Errorf("bleeveRepository.Delete() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("bleeveRepository.Delete() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_bleeveRepository_List(t *testing.T) {

	tests := []struct {
		name    string
		want    []*model.Template
		wantErr bool
	}{
		{
			"List all templates",
			[]*model.Template{
				&model.Template{ID: "template-id1"},
				&model.Template{ID: "template-id2"},
				&model.Template{ID: "template-id3"},
			},
			false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			r, index, clean := newTestRepository(t)
			defer clean()

			for _, templ := range tt.want {
				templ.IID = uuid.NewV4().String()
				err := index.Index(templ.IID, templ)

				if err != nil {
					t.Error("Failed to index template to list", err)
				}
			}
			got, err := r.List()
			if (err != nil) != tt.wantErr {
				t.Errorf("bleeveRepository.List() error = %v, wantErr %v", err, tt.wantErr)
				return
			}

			for _, templ := range got {
				if !containsTemplate(templ, got) {
					t.Errorf("bleeveRepository.List() = %v, want %v", got, templ)
				}

			}

		})
	}
}

func containsTemplate(templ *model.Template, templates []*model.Template) bool {
	for _, ltempl := range templates {
		if reflect.DeepEqual(templ, ltempl) {
			return true
		}
	}
	return false
}

func Test_bleeveRepository_Exists(t *testing.T) {

	type args struct {
		ID string
	}
	tests := []struct {
		name string

		args    args
		want    bool
		wantErr bool
	}{
		{"Template exists", args{"template-id"}, true, false},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			r, index, clean := newTestRepository(t)
			defer clean()
			id := uuid.NewV4().String()
			templ := &model.Template{
				IID: id,
				ID:  "template-id",
			}
			err := index.Index(id, templ)

			if err != nil {
				t.Error("Failed to index template to verify existence", err)
			}
			got, err := r.Exists(tt.args.ID)
			if (err != nil) != tt.wantErr {
				t.Errorf("bleeveRepository.Exists() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("bleeveRepository.Exists() = %v, want %v", got, tt.want)
			}
		})
	}
}
<commit_msg>add extra test for template Exists in bleve repository<commit_after>package bleve

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"testing"

	"github.com/blevesearch/bleve"
	_ "github.com/blevesearch/bleve/analysis/analyzer/keyword"
	"github.com/ironman-project/ironman/template/model"
	"github.com/ironman-project/ironman/template/repository"
	uuid "github.com/satori/go.uuid"
)

func tempIndexPath(t *testing.T) string {
	dir, err := ioutil.TempDir("", "ironman-bleve-test")
	if err != nil {
		t.Fatal("Failed to create test bleve index directory", err)
	}

	indexPath := filepath.Join(dir, "index")
	return indexPath
}

func newTestRepository(t *testing.T) (repository.Repository, bleve.Index, func()) {
	path := tempIndexPath(t)

	index, err := BuildIndex(path)

	if err != nil {
		t.Fatalf("Failed to open test index %s", err)
	}

	r := New(SetIndex(index))

	return r, index, func() {
		err := index.Close()

		if err != nil {
			t.Fatal("Failed to close bleve index", err)
		}
		err = os.RemoveAll(path)
		if err != nil {
			t.Fatal("Failed to clean bleve index", err)
		}
	}
}

func Test_bleeveRepository_Index(t *testing.T) {

	type args struct {
		template *model.Template
	}
	tests := []struct {
		name    string
		args    args
		wantErr bool
	}{
		{
			"Index a template",
			args{
				&model.Template{
					ID:          "test-template",
					Name:        "Test template",
					Description: "This is a test template",
					Generators: []*model.Generator{
						&model.Generator{
							ID:          "test-generator",
							Name:        "Test generator",
							Description: "This is a test generator",
						},
					},
				},
			},
			false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			r, index, clean := newTestRepository(t)
			defer clean()
			var id string
			var err error
			if id, err = r.Index(tt.args.template); (err != nil) != tt.wantErr {
				t.Errorf("bleeveRepository.Index() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			doc, err := index.Document(id)

			if err != nil {
				t.Errorf("bleeveRepository.Index() error = %v, wantErr %v", err, tt.wantErr)
			}

			if doc == nil {
				t.Errorf("bleeveRepository.Index() nil , want %v", tt.args.template)
			}

			got, err := deserialize(doc)

			if err != nil {
				t.Errorf("bleeveRepository.Index()  error = %v", err)
			}

			if got != nil && !reflect.DeepEqual(got, tt.args.template) {
				t.Errorf("bleeveRepository.FindTemplateByID() = %v, want %v", got, tt.args.template)
			}

		})
	}
}

func Test_bleeveRepository_Update(t *testing.T) {

	type args struct {
		template *model.Template
	}
	tests := []struct {
		name     string
		args     args
		template *model.Template
		wantErr  bool
	}{
		{
			"Update template index",
			args{
				&model.Template{
					ID:          "template-id",
					Name:        "Updated name",
					Description: "Updated description",
					Generators: []*model.Generator{
						&model.Generator{
							ID:          "test-generator",
							Name:        "Test generator",
							Description: "This is a test generator",
						},
						&model.Generator{
							ID:          "test-generator2",
							Name:        "Test generator2",
							Description: "This is a test generator 2",
						},
					},
				},
			},
			&model.Template{
				ID: "template-id",
			},
			false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			r, index, clean := newTestRepository(t)
			defer clean()
			id := uuid.NewV4().String()
			tt.template.IID = id
			err := index.Index(id, tt.template)

			if err != nil {
				t.Error("Failed to index template to update", err)
			}

			if err := r.Update(tt.args.template); (err != nil) != tt.wantErr {
				t.Errorf("bleeveRepository.Update() error = %v, wantErr %v", err, tt.wantErr)
			}

			doc, err := index.Document(id)

			if doc == nil {
				t.Error("failed to retreive indexed document with ID", id)
			}

			if err != nil {
				t.Error("failed to retreive indexed document", tt.template, err)
			}

			for _, field := range doc.Fields {

				value := string(field.Value())
				switch field.Name() {
				case "IID":
					if string(value) == "" || (value != id) {
						t.Errorf("bleveRepository.Update() IID = %v want %v", value, id)
					}
				case "ID":
					if string(value) == "" || (value != tt.args.template.ID) {
						t.Errorf("bleveRepository.Update() templateID = %v want %v", value, tt.args.template.ID)
					}
				case "Name":
					if string(value) == "" || (value != tt.args.template.Name) {
						t.Errorf("bleveRepository.Update() templateName = %v want %v", value, tt.args.template.Name)
					}
				case "Description":
					if string(value) == "" || (value != tt.args.template.Description) {
						t.Errorf("bleveRepository.Update() templateDescription = %v want %v", value, tt.args.template.Description)
					}
				case "Generators.ID":
					pos := field.ArrayPositions()[0]
					expectedID := tt.args.template.Generators[pos].ID
					if value != expectedID {
						t.Errorf("bleveRepository.Update() template.Generators[%d].ID = %v want %v", pos, value, expectedID)
					}
				case "Generators.Name":
					pos := field.ArrayPositions()[0]
					expectedName := tt.args.template.Generators[pos].Name
					if value != expectedName {
						t.Errorf("bleveRepository.Update() template.Generators[%d].Name = %v want %v", pos, value, expectedName)
					}
				case "Generators.Description":
					pos := field.ArrayPositions()[0]
					expectedDescription := tt.args.template.Generators[pos].Description
					if value != expectedDescription {
						t.Errorf("bleveRepository.Update() template.Generators[%d].Description = %v want %v", pos, value, expectedDescription)
					}
				default:
					t.Error("doc.Fields should assert field", field.Name(), string(field.Value()))
				}
			}

		})
	}
}

func Test_bleeveRepository_FindTemplateByID(t *testing.T) {

	type args struct {
		ID string
	}
	tests := []struct {
		name    string
		args    args
		want    *model.Template
		wantErr bool
	}{
		{
			"Find by id",
			args{
				"test-template-id",
			},
			&model.Template{
				ID:          "test-template-id",
				Description: "Some description",
			},
			false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			r, index, clean := newTestRepository(t)
			defer clean()
			err := index.Index(uuid.NewV4().String(), tt.want)

			if err != nil {
				t.Errorf("Failed to index template model")
			}

			got, err := r.FindTemplateByID(tt.args.ID)

			if (err != nil) != tt.wantErr {
				t.Errorf("bleeveRepository.FindTemplateByID() error = %v, wantErr %v", err, tt.wantErr)
				return
			}

			if got != nil && !reflect.DeepEqual(got, tt.want) {
				t.Errorf("bleeveRepository.FindTemplateByID() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_bleeveRepository_Delete(t *testing.T) {

	type args struct {
		ID string
	}
	tests := []struct {
		name    string
		args    args
		want    bool
		wantErr bool
	}{
		{
			"Delete template from index",
			args{
				"template-id",
			},
			true,
			false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			r, index, clean := newTestRepository(t)
			defer clean()
			id := uuid.NewV4().String()
			templ := &model.Template{
				IID: id,
				ID:  "template-id",
			}
			err := index.Index(id, templ)

			if err != nil {
				t.Error("Failed to index template to update", err)
			}
			got, err := r.Delete(tt.args.ID)
			if (err != nil) != tt.wantErr {
				t.Errorf("bleeveRepository.Delete() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("bleeveRepository.Delete() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_bleeveRepository_List(t *testing.T) {

	tests := []struct {
		name    string
		want    []*model.Template
		wantErr bool
	}{
		{
			"List all templates",
			[]*model.Template{
				&model.Template{ID: "template-id1"},
				&model.Template{ID: "template-id2"},
				&model.Template{ID: "template-id3"},
			},
			false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			r, index, clean := newTestRepository(t)
			defer clean()

			for _, templ := range tt.want {
				templ.IID = uuid.NewV4().String()
				err := index.Index(templ.IID, templ)

				if err != nil {
					t.Error("Failed to index template to list", err)
				}
			}
			got, err := r.List()
			if (err != nil) != tt.wantErr {
				t.Errorf("bleeveRepository.List() error = %v, wantErr %v", err, tt.wantErr)
				return
			}

			for _, templ := range got {
				if !containsTemplate(templ, got) {
					t.Errorf("bleeveRepository.List() = %v, want %v", got, templ)
				}

			}

		})
	}
}

func containsTemplate(templ *model.Template, templates []*model.Template) bool {
	for _, ltempl := range templates {
		if reflect.DeepEqual(templ, ltempl) {
			return true
		}
	}
	return false
}

func Test_bleeveRepository_Exists(t *testing.T) {

	type args struct {
		ID string
	}
	tests := []struct {
		name string

		args    args
		want    bool
		wantErr bool
	}{
		{"Template exists", args{"template-id"}, true, false},
		{"Template exists", args{"template-id-dont-exists"}, false, false},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			r, index, clean := newTestRepository(t)
			defer clean()
			id := uuid.NewV4().String()
			templ := &model.Template{
				IID: id,
				ID:  "template-id",
			}
			err := index.Index(id, templ)

			if err != nil {
				t.Error("Failed to index template to verify existence", err)
			}
			got, err := r.Exists(tt.args.ID)
			if (err != nil) != tt.wantErr {
				t.Errorf("bleeveRepository.Exists() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("bleeveRepository.Exists() = %v, want %v", got, tt.want)
			}
		})
	}
}
<|endoftext|>


<commit_before>package geom

import (
	"reflect"
	"testing"
)

type testMultiPoint struct {
	layout     Layout
	stride     int
	coords     []Coord
	flatCoords []float64
	bounds     *Bounds
}

func testMultiPointEquals(t *testing.T, mp *MultiPoint, tmp *testMultiPoint) {
	if err := mp.verify(); err != nil {
		t.Error(err)
	}
	if mp.Layout() != tmp.layout {
		t.Errorf("mp.Layout() == %v, want %v", mp.Layout(), tmp.layout)
	}
	if mp.Stride() != tmp.stride {
		t.Errorf("mp.Stride() == %v, want %v", mp.Stride(), tmp.stride)
	}
	if !reflect.DeepEqual(mp.FlatCoords(), tmp.flatCoords) {
		t.Errorf("mp.FlatCoords() == %v, want %v", mp.FlatCoords(), tmp.flatCoords)
	}
	if !reflect.DeepEqual(mp.Coords(), tmp.coords) {
		t.Errorf("mp.Coords() == %v, want %v", mp.Coords(), tmp.coords)
	}
	if !reflect.DeepEqual(mp.Bounds(), tmp.bounds) {
		t.Errorf("mp.Bounds() == %v, want %v", mp.Bounds(), tmp.bounds)
	}
	if got := mp.NumCoords(); got != len(tmp.coords) {
		t.Errorf("mp.NumCoords() == %v, want %v", got, len(tmp.coords))
	}
	for i, c := range tmp.coords {
		if !reflect.DeepEqual(mp.Coord(i), c) {
			t.Errorf("mp.Coord(%v) == %v, want %v", i, mp.Coord(i), c)
		}
	}
}

func TestMultiPoint(t *testing.T) {
	for _, c := range []struct {
		mp  *MultiPoint
		tmp *testMultiPoint
	}{
		{
			mp: NewMultiPoint(XY).MustSetCoords([]Coord{{1, 2}, {3, 4}, {5, 6}}),
			tmp: &testMultiPoint{
				layout:     XY,
				stride:     2,
				coords:     []Coord{{1, 2}, {3, 4}, {5, 6}},
				flatCoords: []float64{1, 2, 3, 4, 5, 6},
				bounds:     NewBounds(XY).Set(1, 2, 5, 6),
			},
		},
		{
			mp: NewMultiPoint(XYZ).MustSetCoords([]Coord{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}),
			tmp: &testMultiPoint{
				layout:     XYZ,
				stride:     3,
				coords:     []Coord{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}},
				flatCoords: []float64{1, 2, 3, 4, 5, 6, 7, 8, 9},
				bounds:     NewBounds(XYZ).Set(1, 2, 3, 7, 8, 9),
			},
		},
		{
			mp: NewMultiPoint(XYM).MustSetCoords([]Coord{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}),
			tmp: &testMultiPoint{
				layout:     XYM,
				stride:     3,
				coords:     []Coord{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}},
				flatCoords: []float64{1, 2, 3, 4, 5, 6, 7, 8, 9},
				bounds:     NewBounds(XYM).Set(1, 2, 3, 7, 8, 9),
			},
		},
		{
			mp: NewMultiPoint(XYZM).MustSetCoords([]Coord{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}),
			tmp: &testMultiPoint{
				layout:     XYZM,
				stride:     4,
				coords:     []Coord{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}},
				flatCoords: []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12},
				bounds:     NewBounds(XYZM).Set(1, 2, 3, 4, 9, 10, 11, 12),
			},
		},
	} {

		testMultiPointEquals(t, c.mp, c.tmp)
	}
}

func TestMultiPointClone(t *testing.T) {
	p1 := NewMultiPoint(XY).MustSetCoords([]Coord{{1, 2}, {3, 4}, {5, 6}})
	if p2 := p1.Clone(); aliases(p1.FlatCoords(), p2.FlatCoords()) {
		t.Error("Clone() should not alias flatCoords")
	}
}

func TestMultiPointPush(t *testing.T) {
	mp := NewMultiPoint(XY)
	testMultiPointEquals(t, mp, &testMultiPoint{
		layout: XY,
		stride: 2,
		coords: []Coord{},
		bounds: NewBounds(XY),
	})
	mp.Push(NewPoint(XY).MustSetCoords(Coord{1, 2}))
	testMultiPointEquals(t, mp, &testMultiPoint{
		layout:     XY,
		stride:     2,
		coords:     []Coord{{1, 2}},
		flatCoords: []float64{1, 2},
		bounds:     NewBounds(XY).Set(1, 2, 1, 2),
	})
	mp.Push(NewPoint(XY).MustSetCoords(Coord{3, 4}))
	testMultiPointEquals(t, mp, &testMultiPoint{
		layout:     XY,
		stride:     2,
		coords:     []Coord{{1, 2}, {3, 4}},
		flatCoords: []float64{1, 2, 3, 4},
		bounds:     NewBounds(XY).Set(1, 2, 3, 4),
	})
}

func TestMultiPointStrideMismatch(t *testing.T) {
	for _, c := range []struct {
		layout Layout
		coords []Coord
		err    error
	}{
		{
			layout: XY,
			coords: nil,
			err:    nil,
		},
		{
			layout: XY,
			coords: []Coord{},
			err:    nil,
		},
		{
			layout: XY,
			coords: []Coord{{1, 2}, {}},
			err:    ErrStrideMismatch{Got: 0, Want: 2},
		},
		{
			layout: XY,
			coords: []Coord{{1, 2}, {1}},
			err:    ErrStrideMismatch{Got: 1, Want: 2},
		},
		{
			layout: XY,
			coords: []Coord{{1, 2}, {3, 4}},
			err:    nil,
		},
		{
			layout: XY,
			coords: []Coord{{1, 2}, {3, 4, 5}},
			err:    ErrStrideMismatch{Got: 3, Want: 2},
		},
	} {
		p := NewMultiPoint(c.layout)
		if _, err := p.SetCoords(c.coords); err != c.err {
			t.Errorf("p.SetCoords(%v) == %v, want %v", c.coords, err, c.err)
		}
	}
}
<commit_msg>Check errors<commit_after>package geom

import (
	"reflect"
	"testing"
)

type testMultiPoint struct {
	layout     Layout
	stride     int
	coords     []Coord
	flatCoords []float64
	bounds     *Bounds
}

func testMultiPointEquals(t *testing.T, mp *MultiPoint, tmp *testMultiPoint) {
	if err := mp.verify(); err != nil {
		t.Error(err)
	}
	if mp.Layout() != tmp.layout {
		t.Errorf("mp.Layout() == %v, want %v", mp.Layout(), tmp.layout)
	}
	if mp.Stride() != tmp.stride {
		t.Errorf("mp.Stride() == %v, want %v", mp.Stride(), tmp.stride)
	}
	if !reflect.DeepEqual(mp.FlatCoords(), tmp.flatCoords) {
		t.Errorf("mp.FlatCoords() == %v, want %v", mp.FlatCoords(), tmp.flatCoords)
	}
	if !reflect.DeepEqual(mp.Coords(), tmp.coords) {
		t.Errorf("mp.Coords() == %v, want %v", mp.Coords(), tmp.coords)
	}
	if !reflect.DeepEqual(mp.Bounds(), tmp.bounds) {
		t.Errorf("mp.Bounds() == %v, want %v", mp.Bounds(), tmp.bounds)
	}
	if got := mp.NumCoords(); got != len(tmp.coords) {
		t.Errorf("mp.NumCoords() == %v, want %v", got, len(tmp.coords))
	}
	for i, c := range tmp.coords {
		if !reflect.DeepEqual(mp.Coord(i), c) {
			t.Errorf("mp.Coord(%v) == %v, want %v", i, mp.Coord(i), c)
		}
	}
}

func TestMultiPoint(t *testing.T) {
	for _, c := range []struct {
		mp  *MultiPoint
		tmp *testMultiPoint
	}{
		{
			mp: NewMultiPoint(XY).MustSetCoords([]Coord{{1, 2}, {3, 4}, {5, 6}}),
			tmp: &testMultiPoint{
				layout:     XY,
				stride:     2,
				coords:     []Coord{{1, 2}, {3, 4}, {5, 6}},
				flatCoords: []float64{1, 2, 3, 4, 5, 6},
				bounds:     NewBounds(XY).Set(1, 2, 5, 6),
			},
		},
		{
			mp: NewMultiPoint(XYZ).MustSetCoords([]Coord{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}),
			tmp: &testMultiPoint{
				layout:     XYZ,
				stride:     3,
				coords:     []Coord{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}},
				flatCoords: []float64{1, 2, 3, 4, 5, 6, 7, 8, 9},
				bounds:     NewBounds(XYZ).Set(1, 2, 3, 7, 8, 9),
			},
		},
		{
			mp: NewMultiPoint(XYM).MustSetCoords([]Coord{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}),
			tmp: &testMultiPoint{
				layout:     XYM,
				stride:     3,
				coords:     []Coord{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}},
				flatCoords: []float64{1, 2, 3, 4, 5, 6, 7, 8, 9},
				bounds:     NewBounds(XYM).Set(1, 2, 3, 7, 8, 9),
			},
		},
		{
			mp: NewMultiPoint(XYZM).MustSetCoords([]Coord{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}),
			tmp: &testMultiPoint{
				layout:     XYZM,
				stride:     4,
				coords:     []Coord{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}},
				flatCoords: []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12},
				bounds:     NewBounds(XYZM).Set(1, 2, 3, 4, 9, 10, 11, 12),
			},
		},
	} {

		testMultiPointEquals(t, c.mp, c.tmp)
	}
}

func TestMultiPointClone(t *testing.T) {
	p1 := NewMultiPoint(XY).MustSetCoords([]Coord{{1, 2}, {3, 4}, {5, 6}})
	if p2 := p1.Clone(); aliases(p1.FlatCoords(), p2.FlatCoords()) {
		t.Error("Clone() should not alias flatCoords")
	}
}

func TestMultiPointPush(t *testing.T) {
	mp := NewMultiPoint(XY)
	testMultiPointEquals(t, mp, &testMultiPoint{
		layout: XY,
		stride: 2,
		coords: []Coord{},
		bounds: NewBounds(XY),
	})
	if err := mp.Push(NewPoint(XY).MustSetCoords(Coord{1, 2})); err != nil {
		t.Error(err)
	}
	testMultiPointEquals(t, mp, &testMultiPoint{
		layout:     XY,
		stride:     2,
		coords:     []Coord{{1, 2}},
		flatCoords: []float64{1, 2},
		bounds:     NewBounds(XY).Set(1, 2, 1, 2),
	})
	if err := mp.Push(NewPoint(XY).MustSetCoords(Coord{3, 4})); err != nil {
		t.Error(err)
	}
	testMultiPointEquals(t, mp, &testMultiPoint{
		layout:     XY,
		stride:     2,
		coords:     []Coord{{1, 2}, {3, 4}},
		flatCoords: []float64{1, 2, 3, 4},
		bounds:     NewBounds(XY).Set(1, 2, 3, 4),
	})
}

func TestMultiPointStrideMismatch(t *testing.T) {
	for _, c := range []struct {
		layout Layout
		coords []Coord
		err    error
	}{
		{
			layout: XY,
			coords: nil,
			err:    nil,
		},
		{
			layout: XY,
			coords: []Coord{},
			err:    nil,
		},
		{
			layout: XY,
			coords: []Coord{{1, 2}, {}},
			err:    ErrStrideMismatch{Got: 0, Want: 2},
		},
		{
			layout: XY,
			coords: []Coord{{1, 2}, {1}},
			err:    ErrStrideMismatch{Got: 1, Want: 2},
		},
		{
			layout: XY,
			coords: []Coord{{1, 2}, {3, 4}},
			err:    nil,
		},
		{
			layout: XY,
			coords: []Coord{{1, 2}, {3, 4, 5}},
			err:    ErrStrideMismatch{Got: 3, Want: 2},
		},
	} {
		p := NewMultiPoint(c.layout)
		if _, err := p.SetCoords(c.coords); err != c.err {
			t.Errorf("p.SetCoords(%v) == %v, want %v", c.coords, err, c.err)
		}
	}
}
<|endoftext|>


<commit_before>package outflux

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"runtime"
	"sync"
	"time"

	"golang.org/x/net/context"
	"golang.org/x/net/context/ctxhttp"

	"go.spiff.io/dagr"
)

// A taskqueue is a simple queue that is used to signal the start and end of a request. It must
// implement begin() and end() methods. The former must block until there are resources available to
// perform a task (the queue is assumed to be aware of this without being given a concrete task) and
// block subsequent tasks while resources are unavailable.
//
// For a taskqueue with a capacity of 1, this would require only one task to begin and block others
// until that task called end(), then repeating the same for another task.
type taskqueue interface {
	begin()
	end()
}

// WriteFunc is any transactional function that accepts an io.Writer. The writer received by the
// WriteFunc is only valid until it returns.
type WriteFunc func(io.Writer) error

// Proxy is a basic InfluxDB line protocol proxy. You may write measurements to it either using dagr or just via
// functions such as fmt.Fprintf. Writes are accumulated for a given duration then POST-ed to the URL the Proxy was
// configured with. It is safe to write to the Proxy while it is sending. Concurrent writes to the Proxy are safe, but
// you should ensure that all writes are atomic and contain all necessary data or occur inside of a Transaction call to
// ensure that nothing slips in between writes.
type Proxy struct {
	destURL *url.URL
	buffer  *bufferchain
	client  *http.Client

	timeout   time.Duration
	flushSize int

	director Director
	requests taskqueue

	retries   int
	delayfunc BackoffFunc

	startOnce sync.Once
	flush     chan flushop
}

// NewURL allocates a new Proxy with a given context, HTTP client, and URL. If the URL is nil, NewURL panics. If the
// context is nil, a new background context is allocated specifically for the Proxy.
//
// Additional configuration can be provided by passing Option values, such as Timeout and FlushSize.
//
// If the HTTP client given is nil, NewURL will use http.DefaultClient.
func NewURL(client *http.Client, destURL *url.URL, opts ...Option) *Proxy {
	if destURL == nil {
		panic("outflux: destination url is nil")
	}

	if client == nil {
		client = http.DefaultClient
	}

	buffers := 6
	if n := (runtime.NumCPU() * 17) / 10; n > buffers {
		buffers = n
	}
	proxy := &Proxy{
		destURL:  destURL,
		buffer:   newBufferchain(buffers, 4000),
		client:   client,
		requests: noRequestLimit{},
		flush:    make(chan flushop),
	}

	DefaultBackoffFunc.configure(proxy)
	DefaultRetries.configure(proxy)

	for _, opt := range opts {
		opt.configure(proxy)
	}

	return proxy
}

// New allocates a new Proxy with the given context, HTTP client, and URL. Unlike NewURL, this will parse the URL first.
// If the URL is empty, New panics. See NewURL for further information.
func New(client *http.Client, destURL string, opts ...Option) *Proxy {
	if destURL == "" {
		panic("outflux: destination url is nil")
	}

	du, err := url.Parse(destURL)
	if err != nil {
		panic(fmt.Sprintf("outflux: error parsing url: %v", err))
	}

	return NewURL(client, du, opts...)
}

type nopWriteCloser struct{ io.Writer }

func (nopWriteCloser) Close() error { return nil }

// flushExcess attempts to flush the proxy's write buffer to InfluxDB if it exceeds the current flush size.
func (w *Proxy) flushExcess() {
	max := w.flushSize
	if max <= 0 || w.buffer.Len() < max {
		return
	}
	go func() {
		if flerr := w.Flush(context.Background()); flerr != nil {
			logf("Flush failed after reaching capacity=%d: %v", max, flerr)
		}
	}()
}

// Write writes the byte slice b to the write buffer of the Proxy. WriteMeasurements should be preferred to ensure that
// the writer is correctly sending InfluxDB line protocol messages, but may be used as a raw writer to the underlying
// Proxy buffers.
func (w *Proxy) Write(b []byte) (int, error) {
	n, err := w.buffer.Write(b)
	w.flushExcess()
	return n, err
}

// Writer returns a locked writer for the Proxy's write buffer. It must be closed to release the lock. Changes to the
// Writer are not counted against the flush size, as the writer is not tracked by the Proxy.
func (w *Proxy) Writer() io.WriteCloser {
	wc := w.buffer.take()
	if w.flushSize > 0 {
		closer := wc.closer
		wc.closer = closerfunc(func() error {
			if err := closer.Close(); err != nil {
				return err
			}
			w.flushExcess()
			return nil
		})
	}
	return wc
}

// Transaction locks the Proxy's write buffer and passes it to fn. Once fn completes, the lock is released. This is
// shorthand for just doing that yourself, in the event that you have a function to pass a writer to but want to avoid
// writing extra code. For example:
//
//      err := proxy.Transaction(func(w io.Writer) {
//              _, err := buf.WriteTo(w)
//              return err
//      })
//
//      if err != nil {
//              // ...
//      }
//
// The WriteFunc given may return an error. This has no effect on the outcome of the transaction and is entirely for
// convenience. If the Proxy is closed, it will return the context error for its closure.
func (w *Proxy) Transaction(fn WriteFunc) (err error) {
	wx := w.Writer()
	defer func() {
		if clerr := logclose(wx); err == nil {
			err = clerr
		}
		w.flushExcess()
	}()
	return fn(wx)
}

// WriteMeasurements writes all measurements in measurements to the Proxy, effectively queueing them for delivery.
func (w *Proxy) WriteMeasurements(measurements ...dagr.Measurement) (n int64, err error) {
	if len(measurements) == 0 {
		return 0, nil
	}

	return dagr.WriteMeasurements(w, measurements...)
}

// WriteMeasurement writes a single measurement to the Proxy.
func (w *Proxy) WriteMeasurement(measurement dagr.Measurement) (n int64, err error) {
	return dagr.WriteMeasurement(w, measurement)
}

// WritePoint writes a single point to the Proxy.
func (w *Proxy) WritePoint(key string, when time.Time, tags dagr.Tags, fields dagr.Fields) (n int64, err error) {
	if key == "" {
		logf("Empty key in point")
		return 0, dagr.ErrEmptyKey
	} else if len(fields) == 0 {
		logf("No fields in point %q", key)
		return 0, dagr.ErrNoFields
	}

	if when.IsZero() {
		when = time.Now()
	}

	return dagr.WriteMeasurement(w, dagr.RawPoint{key, tags, fields, when})
}

// Start creates a goroutine that POSTs buffered data at the given interval. If interval is not a positive duration, the
// Proxy will only send data when you call Flush or if the Proxy has been configured to send when exceeding a certain
// buffer size. The context passed may be used to signal cancellation or provide a hard deadline for the proxy to stop
// by.
//
// The context may not be nil.
func (w *Proxy) Start(ctx context.Context, interval time.Duration) {
	if ctx == nil {
		panic("outflux: context is nil")
	}

	w.startOnce.Do(func() {
		go w.sendEveryInterval(ctx, interval)
	})
}

func (w *Proxy) sendEveryInterval(ctx context.Context, interval time.Duration) {
	defer func() {
		// Flush on close -- use a different context, though.
		ctx := context.Background()
		if timeout := w.timeout; timeout > 0 {
			var cancel context.CancelFunc
			ctx, cancel = context.WithTimeout(ctx, timeout)
			defer cancel()
		}

		w.swapAndSend(ctx, nil)
	}()

	var tick <-chan time.Time
	var timer *time.Timer
	if interval > 0 {
		timer = time.NewTimer(interval)
		tick = timer.C
		defer timer.Stop()
	}

	done := ctx.Done()
	for {
		select {
		case <-done: // Dead
			return
		case <-tick: // Send after interval (ticker is nil if interval <= 0)
			w.swapAndSend(ctx, nil)
		case op := <-w.flush: // Send forced
			w.swapAndSend(op.ctx, op.err)
		}

		// Reset timer if we're using that and not just flushing manually.
		if timer != nil {
			timer.Reset(interval)
		}
	}
}

type flushop struct {
	ctx context.Context
	err chan<- error
}

// Flush forces the Proxy to send out all buffered measurement data as soon as possible. It returns once the flush has
// been received by the Proxy or the Proxy is closed. This only works after Start() has been called.
//
// Flush will block until the write completes and return any relevant error that occurred during the send.
//
// The context may not be nil.
func (w *Proxy) Flush(ctx context.Context) error {
	if ctx == nil {
		panic("outflux: context is nil")
	}
	errch := make(chan error, 1)
	done := ctx.Done()
	select {
	case <-done:
		return ctx.Err()
	case w.flush <- flushop{ctx, errch}:
		select {
		case err := <-errch:
			return err
		case <-done:
			return ctx.Err()
		}
	}
}

func (w *Proxy) swapAndSend(ctx context.Context, out chan<- error) {
	data := w.buffer.flush()
	if len(data) == 0 {
		// Nothing to do.
		return
	}

	go func() {
		err := w.sendData(ctx, data, w.retries)
		if out != nil {
			out <- err
		}
	}()
}

func (w *Proxy) sendData(ctx context.Context, data []byte, retries int) error {
	var (
		done  = ctx.Done()
		retry bool
		err   error
	)

retryLoop:
	for i := 0; i <= retries; i++ {
		retry, err = w.send(ctx, bytes.NewReader(data))
		if err == nil {
			return nil
		}

		if !retry {
			logf("Error sending payload of size=%d to %s - will not retry: %v", len(data), w.destURL.Host, err)
			break retryLoop
		}

		next := w.delayfunc(i+1, retries)
		if next <= 0 {
			// Send now. If there's a context error, it'll be caught by send().
			continue
		}

		logf("Error sending payload of size=%d to %s - will retry in %v: %v", len(data), w.destURL.Host, next, err)
		select {
		case <-time.After(next):
		case <-done:
			break retryLoop
		}
	}

	if err != nil {
		logf("Failed to send payload of size=%d to %s: %v", len(data), w.destURL.Host, err)
	}

	return err
}

func (w *Proxy) send(ctx context.Context, body *bytes.Reader) (retry bool, err error) {
	if err = ctx.Err(); err != nil {
		// Whatever is returned here will necessarily all future retries.
		return false, err
	}

	if timeout := w.timeout; timeout > 0 {
		var cancel context.CancelFunc
		ctx, cancel = context.WithTimeout(ctx, timeout)
		defer cancel()
	}

	w.requests.begin()
	defer w.requests.end()

	req, err := http.NewRequest("POST", w.destURL.String(), body)
	if err != nil {
		// Error occurred creating the request itself, we can't do anything
		return false, err
	}

	req.Header.Set("Content-Type", "")
	if w.director != nil {
		if err := w.director(req); err != nil {
			return false, err
		}
	}

	resp, err := ctxhttp.Do(ctx, w.client, req)
	if err != nil {
		if err == context.Canceled {
			return false, err
		}
		return true, err
	}
	defer func() {
		// Discard response.
		if _, copyerr := io.Copy(ioutil.Discard, resp.Body); copyerr != nil {
			logf("Error discarding InfluxDB response body: %v", copyerr)
		}
		logclose(resp.Body)
	}()

	// Per InfluxDB docs (0.11-ish I think, but possibly earlier), anything other than 204,
	// including status 200, is an error.
	if resp.StatusCode != 204 {
		var sterr = &BadStatusError{Code: resp.StatusCode}
		sterr.Body, sterr.Err = ioutil.ReadAll(resp.Body)
		return false, sterr // InfluxDB rejected the response, so discard it.
	}

	return false, nil
}
<commit_msg>Fix goroutine leak in outflux for empty flush<commit_after>package outflux

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"runtime"
	"sync"
	"time"

	"golang.org/x/net/context"
	"golang.org/x/net/context/ctxhttp"

	"go.spiff.io/dagr"
)

// A taskqueue is a simple queue that is used to signal the start and end of a request. It must
// implement begin() and end() methods. The former must block until there are resources available to
// perform a task (the queue is assumed to be aware of this without being given a concrete task) and
// block subsequent tasks while resources are unavailable.
//
// For a taskqueue with a capacity of 1, this would require only one task to begin and block others
// until that task called end(), then repeating the same for another task.
type taskqueue interface {
	begin()
	end()
}

// WriteFunc is any transactional function that accepts an io.Writer. The writer received by the
// WriteFunc is only valid until it returns.
type WriteFunc func(io.Writer) error

// Proxy is a basic InfluxDB line protocol proxy. You may write measurements to it either using dagr or just via
// functions such as fmt.Fprintf. Writes are accumulated for a given duration then POST-ed to the URL the Proxy was
// configured with. It is safe to write to the Proxy while it is sending. Concurrent writes to the Proxy are safe, but
// you should ensure that all writes are atomic and contain all necessary data or occur inside of a Transaction call to
// ensure that nothing slips in between writes.
type Proxy struct {
	destURL *url.URL
	buffer  *bufferchain
	client  *http.Client

	timeout   time.Duration
	flushSize int

	director Director
	requests taskqueue

	retries   int
	delayfunc BackoffFunc

	startOnce sync.Once
	flush     chan flushop
}

// NewURL allocates a new Proxy with a given context, HTTP client, and URL. If the URL is nil, NewURL panics. If the
// context is nil, a new background context is allocated specifically for the Proxy.
//
// Additional configuration can be provided by passing Option values, such as Timeout and FlushSize.
//
// If the HTTP client given is nil, NewURL will use http.DefaultClient.
func NewURL(client *http.Client, destURL *url.URL, opts ...Option) *Proxy {
	if destURL == nil {
		panic("outflux: destination url is nil")
	}

	if client == nil {
		client = http.DefaultClient
	}

	buffers := 6
	if n := (runtime.NumCPU() * 17) / 10; n > buffers {
		buffers = n
	}
	proxy := &Proxy{
		destURL:  destURL,
		buffer:   newBufferchain(buffers, 4000),
		client:   client,
		requests: noRequestLimit{},
		flush:    make(chan flushop),
	}

	DefaultBackoffFunc.configure(proxy)
	DefaultRetries.configure(proxy)

	for _, opt := range opts {
		opt.configure(proxy)
	}

	return proxy
}

// New allocates a new Proxy with the given context, HTTP client, and URL. Unlike NewURL, this will parse the URL first.
// If the URL is empty, New panics. See NewURL for further information.
func New(client *http.Client, destURL string, opts ...Option) *Proxy {
	if destURL == "" {
		panic("outflux: destination url is nil")
	}

	du, err := url.Parse(destURL)
	if err != nil {
		panic(fmt.Sprintf("outflux: error parsing url: %v", err))
	}

	return NewURL(client, du, opts...)
}

type nopWriteCloser struct{ io.Writer }

func (nopWriteCloser) Close() error { return nil }

// flushExcess attempts to flush the proxy's write buffer to InfluxDB if it exceeds the current flush size.
func (w *Proxy) flushExcess() {
	max := w.flushSize
	if max <= 0 || w.buffer.Len() < max {
		return
	}
	go func() {
		if flerr := w.Flush(context.Background()); flerr != nil {
			logf("Flush failed after reaching capacity=%d: %v", max, flerr)
		}
	}()
}

// Write writes the byte slice b to the write buffer of the Proxy. WriteMeasurements should be preferred to ensure that
// the writer is correctly sending InfluxDB line protocol messages, but may be used as a raw writer to the underlying
// Proxy buffers.
func (w *Proxy) Write(b []byte) (int, error) {
	n, err := w.buffer.Write(b)
	w.flushExcess()
	return n, err
}

// Writer returns a locked writer for the Proxy's write buffer. It must be closed to release the lock. Changes to the
// Writer are not counted against the flush size, as the writer is not tracked by the Proxy.
func (w *Proxy) Writer() io.WriteCloser {
	wc := w.buffer.take()
	if w.flushSize > 0 {
		closer := wc.closer
		wc.closer = closerfunc(func() error {
			if err := closer.Close(); err != nil {
				return err
			}
			w.flushExcess()
			return nil
		})
	}
	return wc
}

// Transaction locks the Proxy's write buffer and passes it to fn. Once fn completes, the lock is released. This is
// shorthand for just doing that yourself, in the event that you have a function to pass a writer to but want to avoid
// writing extra code. For example:
//
//      err := proxy.Transaction(func(w io.Writer) {
//              _, err := buf.WriteTo(w)
//              return err
//      })
//
//      if err != nil {
//              // ...
//      }
//
// The WriteFunc given may return an error. This has no effect on the outcome of the transaction and is entirely for
// convenience. If the Proxy is closed, it will return the context error for its closure.
func (w *Proxy) Transaction(fn WriteFunc) (err error) {
	wx := w.Writer()
	defer func() {
		if clerr := logclose(wx); err == nil {
			err = clerr
		}
		w.flushExcess()
	}()
	return fn(wx)
}

// WriteMeasurements writes all measurements in measurements to the Proxy, effectively queueing them for delivery.
func (w *Proxy) WriteMeasurements(measurements ...dagr.Measurement) (n int64, err error) {
	if len(measurements) == 0 {
		return 0, nil
	}

	return dagr.WriteMeasurements(w, measurements...)
}

// WriteMeasurement writes a single measurement to the Proxy.
func (w *Proxy) WriteMeasurement(measurement dagr.Measurement) (n int64, err error) {
	return dagr.WriteMeasurement(w, measurement)
}

// WritePoint writes a single point to the Proxy.
func (w *Proxy) WritePoint(key string, when time.Time, tags dagr.Tags, fields dagr.Fields) (n int64, err error) {
	if key == "" {
		logf("Empty key in point")
		return 0, dagr.ErrEmptyKey
	} else if len(fields) == 0 {
		logf("No fields in point %q", key)
		return 0, dagr.ErrNoFields
	}

	if when.IsZero() {
		when = time.Now()
	}

	return dagr.WriteMeasurement(w, dagr.RawPoint{key, tags, fields, when})
}

// Start creates a goroutine that POSTs buffered data at the given interval. If interval is not a positive duration, the
// Proxy will only send data when you call Flush or if the Proxy has been configured to send when exceeding a certain
// buffer size. The context passed may be used to signal cancellation or provide a hard deadline for the proxy to stop
// by.
//
// The context may not be nil.
func (w *Proxy) Start(ctx context.Context, interval time.Duration) {
	if ctx == nil {
		panic("outflux: context is nil")
	}

	w.startOnce.Do(func() {
		go w.sendEveryInterval(ctx, interval)
	})
}

func (w *Proxy) sendEveryInterval(ctx context.Context, interval time.Duration) {
	defer func() {
		// Flush on close -- use a different context, though.
		ctx := context.Background()
		if timeout := w.timeout; timeout > 0 {
			var cancel context.CancelFunc
			ctx, cancel = context.WithTimeout(ctx, timeout)
			defer cancel()
		}

		w.swapAndSend(ctx, nil)
	}()

	var tick <-chan time.Time
	var timer *time.Timer
	if interval > 0 {
		timer = time.NewTimer(interval)
		tick = timer.C
		defer timer.Stop()
	}

	done := ctx.Done()
	for {
		select {
		case <-done: // Dead
			return
		case <-tick: // Send after interval (ticker is nil if interval <= 0)
			w.swapAndSend(ctx, nil)
		case op := <-w.flush: // Send forced
			w.swapAndSend(op.ctx, op.err)
		}

		// Reset timer if we're using that and not just flushing manually.
		if timer != nil {
			timer.Reset(interval)
		}
	}
}

type flushop struct {
	ctx context.Context
	err chan<- error
}

// Flush forces the Proxy to send out all buffered measurement data as soon as possible. It returns once the flush has
// been received by the Proxy or the Proxy is closed. This only works after Start() has been called.
//
// Flush will block until the write completes and return any relevant error that occurred during the send.
//
// The context may not be nil.
func (w *Proxy) Flush(ctx context.Context) error {
	if ctx == nil {
		panic("outflux: context is nil")
	}
	errch := make(chan error, 1)
	done := ctx.Done()
	select {
	case <-done:
		return ctx.Err()
	case w.flush <- flushop{ctx, errch}:
		select {
		case err := <-errch:
			return err
		case <-done:
			return ctx.Err()
		}
	}
}

func (w *Proxy) swapAndSend(ctx context.Context, out chan<- error) {
	data := w.buffer.flush()
	if len(data) == 0 {
		// Nothing to do.
		if out != nil {
			out <- nil
		}
		return
	}

	go func() {
		err := w.sendData(ctx, data, w.retries)
		if out != nil {
			out <- err
		}
	}()
}

func (w *Proxy) sendData(ctx context.Context, data []byte, retries int) error {
	var (
		done  = ctx.Done()
		retry bool
		err   error
	)

retryLoop:
	for i := 0; i <= retries; i++ {
		retry, err = w.send(ctx, bytes.NewReader(data))
		if err == nil {
			return nil
		}

		if !retry {
			logf("Error sending payload of size=%d to %s - will not retry: %v", len(data), w.destURL.Host, err)
			break retryLoop
		}

		next := w.delayfunc(i+1, retries)
		if next <= 0 {
			// Send now. If there's a context error, it'll be caught by send().
			continue
		}

		logf("Error sending payload of size=%d to %s - will retry in %v: %v", len(data), w.destURL.Host, next, err)
		select {
		case <-time.After(next):
		case <-done:
			break retryLoop
		}
	}

	if err != nil {
		logf("Failed to send payload of size=%d to %s: %v", len(data), w.destURL.Host, err)
	}

	return err
}

func (w *Proxy) send(ctx context.Context, body *bytes.Reader) (retry bool, err error) {
	if err = ctx.Err(); err != nil {
		// Whatever is returned here will necessarily all future retries.
		return false, err
	}

	if timeout := w.timeout; timeout > 0 {
		var cancel context.CancelFunc
		ctx, cancel = context.WithTimeout(ctx, timeout)
		defer cancel()
	}

	w.requests.begin()
	defer w.requests.end()

	req, err := http.NewRequest("POST", w.destURL.String(), body)
	if err != nil {
		// Error occurred creating the request itself, we can't do anything
		return false, err
	}

	req.Header.Set("Content-Type", "")
	if w.director != nil {
		if err := w.director(req); err != nil {
			return false, err
		}
	}

	resp, err := ctxhttp.Do(ctx, w.client, req)
	if err != nil {
		if err == context.Canceled {
			return false, err
		}
		return true, err
	}
	defer func() {
		// Discard response.
		if _, copyerr := io.Copy(ioutil.Discard, resp.Body); copyerr != nil {
			logf("Error discarding InfluxDB response body: %v", copyerr)
		}
		logclose(resp.Body)
	}()

	// Per InfluxDB docs (0.11-ish I think, but possibly earlier), anything other than 204,
	// including status 200, is an error.
	if resp.StatusCode != 204 {
		var sterr = &BadStatusError{Code: resp.StatusCode}
		sterr.Body, sterr.Err = ioutil.ReadAll(resp.Body)
		return false, sterr // InfluxDB rejected the response, so discard it.
	}

	return false, nil
}
<|endoftext|>


<commit_before>// +build integration

package main

import (
	"bufio"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"testing"
	"time"

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

var dockerHostname string

func init() {
	if dockerHost := os.Getenv("DOCKER_HOST"); dockerHost == "" {
		dockerHostname = "localhost"
	} else {
		dockerHostURL, err := url.Parse(dockerHost)
		if err != nil {
			panic(err)
		}

		dockerHostname = dockerHostURL.Host
	}
}

func TestMixedCalls(t *testing.T) {
	requestBody := []byte(`POST /batch HTTP/1.1
Host: ` + dockerHostname + `:8080
Content-Length: 469
Content-Type: multipart/mixed; boundary="===============7330845974216740156=="

--===============7330845974216740156==
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: <b29c5de2-0db4-490b-b421-6a51b598bd22+1>

GET /some/thing HTTP/1.1
Host: serverA:8080
Accept: */*

--===============7330845974216740156==
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: <b29c5de2-0db4-490b-b421-6a51b598bd22+2>

POST http://serverB:8080/weeee HTTP/1.1
Accept: */*

--===============7330845974216740156==--`)

	t.Log(string(requestBody))

	conn, err := net.DialTimeout("tcp", dockerHostname+":8080", 5*time.Second)
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	_, err = conn.Write(requestBody)
	if err != nil {
		panic(err)
	}

	resp, err := http.ReadResponse(bufio.NewReader(conn), nil)
	if err != nil {
		panic(err)
	}

	assert.Equal(t, http.StatusOK, resp.StatusCode)

	logResponse(t, resp)
}

func logResponse(t *testing.T, resp *http.Response) {
	dump, err := httputil.DumpResponse(resp, true)
	if err != nil {
		t.Log(err)
		return
	}

	t.Log(string(dump))
}
<commit_msg>Fix docker host detection from env variable<commit_after>// +build integration

package main

import (
	"bufio"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"strings"
	"testing"
	"time"

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

var dockerHostname string

func init() {
	if dockerHost := os.Getenv("DOCKER_HOST"); dockerHost == "" {
		dockerHostname = "localhost"
	} else {
		dockerHostURL, err := url.Parse(dockerHost)
		if err != nil {
			panic(err)
		}

		dockerHostname = strings.Split(dockerHostURL.Host, ":")[0]
	}
}

func TestMixedCalls(t *testing.T) {
	requestBody := []byte(`POST /batch HTTP/1.1
Host: ` + dockerHostname + `:8080
Content-Length: 469
Content-Type: multipart/mixed; boundary="===============7330845974216740156=="

--===============7330845974216740156==
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: <b29c5de2-0db4-490b-b421-6a51b598bd22+1>

GET /some/thing HTTP/1.1
Host: serverA:8080
Accept: */*

--===============7330845974216740156==
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: <b29c5de2-0db4-490b-b421-6a51b598bd22+2>

POST http://serverB:8080/weeee HTTP/1.1
Accept: */*

--===============7330845974216740156==--`)

	t.Log(string(requestBody))

	conn, err := net.DialTimeout("tcp", dockerHostname+":8080", 5*time.Second)
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	_, err = conn.Write(requestBody)
	if err != nil {
		panic(err)
	}

	resp, err := http.ReadResponse(bufio.NewReader(conn), nil)
	if err != nil {
		panic(err)
	}

	assert.Equal(t, http.StatusOK, resp.StatusCode)

	logResponse(t, resp)
}

func logResponse(t *testing.T, resp *http.Response) {
	dump, err := httputil.DumpResponse(resp, true)
	if err != nil {
		t.Log(err)
		return
	}

	t.Log(string(dump))
}
<|endoftext|>


<commit_before>package stripe

import (
	"context"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"net/http"
	"net/url"
	"time"

	"github.com/stripe/stripe-go/form"
)

const (
	endbefore  = "ending_before"
	startafter = "starting_after"
)

// Params is the structure that contains the common properties
// of any *Params structure.
type Params struct {
	// Account is deprecated form of StripeAccount that will do the same thing.
	// Please use StripeAccount instead.
	Account string `form:"-"` // Passed as header

	// Context used for request. It may carry deadlines, cancelation signals,
	// and other request-scoped values across API boundaries and between
	// processes.
	Context context.Context `form:"-"`

	Exp   []string     `form:"expand"`
	Extra *ExtraValues `form:"*"`

	// Headers may be used to provide extra header lines on the HTTP request.
	Headers http.Header `form:"-"`

	IdempotencyKey string            `form:"-"` // Passed as header
	Meta           map[string]string `form:"metadata"`

	// StripeAccount may contain the ID of a connected account. By including
	// this field, the request is made as if it originated from the connected
	// account instead of under the account of the owner of the configured
	// Stripe key.
	StripeAccount string `form:"-"` // Passed as header
}

// ExtraValues are extra parameters that are attached to an API request.
// They're implemented as a custom type so that they can have their own
// AppendTo implementation.
type ExtraValues struct {
	url.Values `form:"-"` // See custom AppendTo implementation
}

// AppendTo implements custom form encoding for extra parameter values.
func (v ExtraValues) AppendTo(body *form.Values, keyParts []string) {
	for k, vs := range v.Values {
		for _, v := range vs {
			body.Add(form.FormatKey(append(keyParts, k)), v)
		}
	}
}

// ListParams is the structure that contains the common properties
// of any *ListParams structure.
type ListParams struct {
	// Context used for request. It may carry deadlines, cancelation signals,
	// and other request-scoped values across API boundaries and between
	// processes.
	Context context.Context `form:"-"`

	End     string   `form:"ending_before"`
	Exp     []string `form:"expand"`
	Filters Filters  `form:"*"`
	Limit   int      `form:"limit"`

	// Single specifies whether this is a single page iterator. By default,
	// listing through an iterator will automatically grab additional pages as
	// the query progresses. To change this behavior and just load a single
	// page, set this to true.
	Single bool `form:"-"` // Not an API parameter

	Start string `form:"starting_after"`

	// StripeAccount may contain the ID of a connected account. By including
	// this field, the request is made as if it originated from the connected
	// account instead of under the account of the owner of the configured
	// Stripe key.
	StripeAccount string `form:"-"` // Passed as header
}

// ListMeta is the structure that contains the common properties
// of List iterators. The Count property is only populated if the
// total_count include option is passed in (see tests for example).
type ListMeta struct {
	Count uint32 `json:"total_count"`
	More  bool   `json:"has_more"`
	URL   string `json:"url"`
}

// RangeQueryParams are a set of generic request parameters that are used on
// list endpoints to filter their results by some timestamp.
type RangeQueryParams struct {
	// GreaterThan specifies that values should be a greater than this
	// timestamp.
	GreaterThan int64 `form:"gt"`

	// GreaterThanOrEqual specifies that values should be greater than or equal
	// to this timestamp.
	GreaterThanOrEqual int64 `form:"gte"`

	// LesserThan specifies that values should be lesser than this timetamp.
	LesserThan int64 `form:"lt"`

	// LesserThanOrEqual specifies that values should be lesser than or
	// equalthis timetamp.
	LesserThanOrEqual int64 `form:"lte"`
}

// Filters is a structure that contains a collection of filters for list-related APIs.
type Filters struct {
	f []*filter `form:"-"` // See custom AppendTo implementation
}

// AddFilter adds a new filter with a given key, op and value.
func (f *Filters) AddFilter(key, op, value string) {
	filter := &filter{Key: key, Op: op, Val: value}
	f.f = append(f.f, filter)
}

// AppendTo implements custom form encoding for filters.
func (f Filters) AppendTo(body *form.Values, keyParts []string) {
	if len(f.f) > 0 {
		for _, v := range f.f {
			if len(v.Op) > 0 {
				body.Add(form.FormatKey(append(keyParts, v.Key, v.Op)), v.Val)
			} else {
				body.Add(form.FormatKey(append(keyParts, v.Key)), v.Val)
			}
		}
	}
}

// filter is the structure that contains a filter for list-related APIs.
// It ends up passing query string parameters in the format key[op]=value.
type filter struct {
	Key, Op, Val string
}

// NewIdempotencyKey generates a new idempotency key that
// can be used on a request.
func NewIdempotencyKey() string {
	now := time.Now().UnixNano()
	buf := make([]byte, 4)
	rand.Read(buf)
	return fmt.Sprintf("%v_%v", now, base64.URLEncoding.EncodeToString(buf)[:6])
}

// SetAccount sets a value for the Stripe-Account header.
func (p *Params) SetAccount(val string) {
	p.Account = val
	p.StripeAccount = val
}

// SetStripeAccount sets a value for the Stripe-Account header.
func (p *Params) SetStripeAccount(val string) {
	p.StripeAccount = val
}

// Expand appends a new field to expand.
func (p *Params) Expand(f string) {
	p.Exp = append(p.Exp, f)
}

// AddMeta adds a new key-value pair to the Metadata.
func (p *Params) AddMeta(key, value string) {
	if p.Meta == nil {
		p.Meta = make(map[string]string)
	}

	p.Meta[key] = value
}

// AddExtra adds a new arbitrary key-value pair to the request data
func (p *Params) AddExtra(key, value string) {
	if p.Extra == nil {
		p.Extra = &ExtraValues{Values: make(url.Values)}
	}

	p.Extra.Add(key, value)
}

// Expand appends a new field to expand.
func (p *ListParams) Expand(f string) {
	p.Exp = append(p.Exp, f)
}

// ToParams converts a ListParams to a Params by moving over any fields that
// have valid targets in the new type. This is useful because fields in
// Params can be injected directly into an http.Request while generally
// ListParams is only used to build a set of parameters.
func (p *ListParams) ToParams() *Params {
	return &Params{
		Context:       p.Context,
		StripeAccount: p.StripeAccount,
	}
}
<commit_msg>Expand on Context comment for additional clarity<commit_after>package stripe

import (
	"context"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"net/http"
	"net/url"
	"time"

	"github.com/stripe/stripe-go/form"
)

const (
	endbefore  = "ending_before"
	startafter = "starting_after"
)

// Params is the structure that contains the common properties
// of any *Params structure.
type Params struct {
	// Account is deprecated form of StripeAccount that will do the same thing.
	// Please use StripeAccount instead.
	Account string `form:"-"` // Passed as header

	// Context used for request. It may carry deadlines, cancelation signals,
	// and other request-scoped values across API boundaries and between
	// processes.
	//
	// Note that a cancelled or timed out context does not provide any
	// guarantee whether the operation was or was not completed on Stripe's API
	// servers. For certainty, you must either retry with the same idempotency
	// key or query the state of the API.
	Context context.Context `form:"-"`

	Exp   []string     `form:"expand"`
	Extra *ExtraValues `form:"*"`

	// Headers may be used to provide extra header lines on the HTTP request.
	Headers http.Header `form:"-"`

	IdempotencyKey string            `form:"-"` // Passed as header
	Meta           map[string]string `form:"metadata"`

	// StripeAccount may contain the ID of a connected account. By including
	// this field, the request is made as if it originated from the connected
	// account instead of under the account of the owner of the configured
	// Stripe key.
	StripeAccount string `form:"-"` // Passed as header
}

// ExtraValues are extra parameters that are attached to an API request.
// They're implemented as a custom type so that they can have their own
// AppendTo implementation.
type ExtraValues struct {
	url.Values `form:"-"` // See custom AppendTo implementation
}

// AppendTo implements custom form encoding for extra parameter values.
func (v ExtraValues) AppendTo(body *form.Values, keyParts []string) {
	for k, vs := range v.Values {
		for _, v := range vs {
			body.Add(form.FormatKey(append(keyParts, k)), v)
		}
	}
}

// ListParams is the structure that contains the common properties
// of any *ListParams structure.
type ListParams struct {
	// Context used for request. It may carry deadlines, cancelation signals,
	// and other request-scoped values across API boundaries and between
	// processes.
	//
	// Note that a cancelled or timed out context does not provide any
	// guarantee whether the operation was or was not completed on Stripe's API
	// servers. For certainty, you must either retry with the same idempotency
	// key or query the state of the API.
	Context context.Context `form:"-"`

	End     string   `form:"ending_before"`
	Exp     []string `form:"expand"`
	Filters Filters  `form:"*"`
	Limit   int      `form:"limit"`

	// Single specifies whether this is a single page iterator. By default,
	// listing through an iterator will automatically grab additional pages as
	// the query progresses. To change this behavior and just load a single
	// page, set this to true.
	Single bool `form:"-"` // Not an API parameter

	Start string `form:"starting_after"`

	// StripeAccount may contain the ID of a connected account. By including
	// this field, the request is made as if it originated from the connected
	// account instead of under the account of the owner of the configured
	// Stripe key.
	StripeAccount string `form:"-"` // Passed as header
}

// ListMeta is the structure that contains the common properties
// of List iterators. The Count property is only populated if the
// total_count include option is passed in (see tests for example).
type ListMeta struct {
	Count uint32 `json:"total_count"`
	More  bool   `json:"has_more"`
	URL   string `json:"url"`
}

// RangeQueryParams are a set of generic request parameters that are used on
// list endpoints to filter their results by some timestamp.
type RangeQueryParams struct {
	// GreaterThan specifies that values should be a greater than this
	// timestamp.
	GreaterThan int64 `form:"gt"`

	// GreaterThanOrEqual specifies that values should be greater than or equal
	// to this timestamp.
	GreaterThanOrEqual int64 `form:"gte"`

	// LesserThan specifies that values should be lesser than this timetamp.
	LesserThan int64 `form:"lt"`

	// LesserThanOrEqual specifies that values should be lesser than or
	// equalthis timetamp.
	LesserThanOrEqual int64 `form:"lte"`
}

// Filters is a structure that contains a collection of filters for list-related APIs.
type Filters struct {
	f []*filter `form:"-"` // See custom AppendTo implementation
}

// AddFilter adds a new filter with a given key, op and value.
func (f *Filters) AddFilter(key, op, value string) {
	filter := &filter{Key: key, Op: op, Val: value}
	f.f = append(f.f, filter)
}

// AppendTo implements custom form encoding for filters.
func (f Filters) AppendTo(body *form.Values, keyParts []string) {
	if len(f.f) > 0 {
		for _, v := range f.f {
			if len(v.Op) > 0 {
				body.Add(form.FormatKey(append(keyParts, v.Key, v.Op)), v.Val)
			} else {
				body.Add(form.FormatKey(append(keyParts, v.Key)), v.Val)
			}
		}
	}
}

// filter is the structure that contains a filter for list-related APIs.
// It ends up passing query string parameters in the format key[op]=value.
type filter struct {
	Key, Op, Val string
}

// NewIdempotencyKey generates a new idempotency key that
// can be used on a request.
func NewIdempotencyKey() string {
	now := time.Now().UnixNano()
	buf := make([]byte, 4)
	rand.Read(buf)
	return fmt.Sprintf("%v_%v", now, base64.URLEncoding.EncodeToString(buf)[:6])
}

// SetAccount sets a value for the Stripe-Account header.
func (p *Params) SetAccount(val string) {
	p.Account = val
	p.StripeAccount = val
}

// SetStripeAccount sets a value for the Stripe-Account header.
func (p *Params) SetStripeAccount(val string) {
	p.StripeAccount = val
}

// Expand appends a new field to expand.
func (p *Params) Expand(f string) {
	p.Exp = append(p.Exp, f)
}

// AddMeta adds a new key-value pair to the Metadata.
func (p *Params) AddMeta(key, value string) {
	if p.Meta == nil {
		p.Meta = make(map[string]string)
	}

	p.Meta[key] = value
}

// AddExtra adds a new arbitrary key-value pair to the request data
func (p *Params) AddExtra(key, value string) {
	if p.Extra == nil {
		p.Extra = &ExtraValues{Values: make(url.Values)}
	}

	p.Extra.Add(key, value)
}

// Expand appends a new field to expand.
func (p *ListParams) Expand(f string) {
	p.Exp = append(p.Exp, f)
}

// ToParams converts a ListParams to a Params by moving over any fields that
// have valid targets in the new type. This is useful because fields in
// Params can be injected directly into an http.Request while generally
// ListParams is only used to build a set of parameters.
func (p *ListParams) ToParams() *Params {
	return &Params{
		Context:       p.Context,
		StripeAccount: p.StripeAccount,
	}
}
<|endoftext|>


<commit_before>package templates

func init() {
	registerTemplate("channel.html", `
{{template "header.html" .}}
{{$g := .GraphOptions}}
{{$firstHost := .FirstHost}}

Streams /

{{.Topic}} /

{{.Channel}}

Topic: {{.Topic}}

Channel: {{.Channel}}

{{if not .ChannelStats}}

Notice
 No producers exist for this topic/channel.

See Lookup for more information.

{{else}}

        <form action="/empty_channel" method="POST">
            <input type="hidden" name="topic" value="{{.ChannelStats.TopicName}}">
            <input type="hidden" name="channel" value="{{.ChannelStats.ChannelName}}">
            <button class="btn btn-medium btn-warning" type="submit">Empty Queue</button>
        </form>

        <form action="/delete_channel" method="POST">
            <input type="hidden" name="topic" value="{{.ChannelStats.TopicName}}">
            <input type="hidden" name="channel" value="{{.ChannelStats.ChannelName}}">
            <button class="btn btn-medium btn-danger" type="submit">Delete Channel</button>
        </form>

        {{if .ChannelStats.Paused}}
        <form action="/unpause_channel" method="POST">
            <input type="hidden" name="topic" value="{{.ChannelStats.TopicName}}">
            <input type="hidden" name="channel" value="{{.ChannelStats.ChannelName}}">
            <button class="btn btn-medium btn-success" type="submit">UnPause Channel</button>
        </form>
        {{else}}
        <form action="/pause_channel" method="POST">
            <input type="hidden" name="topic" value="{{.ChannelStats.TopicName}}">
            <input type="hidden" name="channel" value="{{.ChannelStats.ChannelName}}">
            <button class="btn btn-medium btn-inverse" type="submit">Pause Channel</button>
        </form>
        {{end}}

Channel

            Message Queues
            Statistics
            {{if $firstHost.E2eProcessingLatency.Percentiles}}
              E2E Processing Latency
            {{end}}

            NSQd Host
            Depth
            Memory + Disk
            In-Flight
            Deferred
            Requeued
            Timed Out
            Messages
            {{if $g.Enabled}}Rate	{{end}}
            Connections
            {{range $e2e := $firstHost.E2eProcessingLatency.Percentiles}}
              {{$e2e.quantile | floatToPercent}}{{$e2e.quantile | percSuffix}}
            {{end}}

{{range $c := .ChannelStats.HostStats}}

            {{$c.HostAddress}}{{if $c.Paused}} paused{{end}}
            {{$c.Depth | commafy}}
            {{$c.MemoryDepth | commafy}} + {{$c.BackendDepth | commafy}}
            {{$c.InFlightCount | commafy}}
            {{$c.DeferredCount | commafy}}
            {{$c.RequeueCount | commafy}}
            {{$c.TimeoutCount | commafy}}
            {{$c.MessageCount | commafy}}
            {{if $g.Enabled}}	 {{end}}
            {{$c.ClientCount}}
            {{if $c.E2eProcessingLatency.Percentiles}}
              {{range $e2e := $c.E2eProcessingLatency.Percentiles}}

                  {{$e2e.average | nanotohuman}}

              {{end}}
            {{end}}

        {{if $g.Enabled}}

            {{if $c.E2eProcessingLatency.Percentiles}}

            {{end}}

        {{end}}

{{ end }}
{{ with $c := .ChannelStats }}

            Total:
            {{$c.Depth | commafy}}
            {{$c.MemoryDepth | commafy}} + {{$c.BackendDepth | commafy}}
            {{$c.InFlightCount | commafy}}
            {{$c.DeferredCount | commafy}}
            {{$c.RequeueCount | commafy}}
            {{$c.TimeoutCount | commafy}}
            {{$c.MessageCount | commafy}}
            {{if $g.Enabled}}	 {{end}}
            {{$c.ClientCount}}
            {{if $c.E2eProcessingLatency.Percentiles}}
              {{range $e2e := $c.E2eProcessingLatency.Percentiles}}

                  {{$e2e.average | nanotohuman}}

              {{end}}
            {{end}}

        {{if $g.Enabled}}

            {{if $c.E2eProcessingLatency.Percentiles}}

            {{end}}

        {{end}}

{{ end }}

Client Connections

{{if not .ChannelStats.Clients}}

Notice
No clients connected to this channel

{{else}}

        Client Host
        Protocol
        Attributes
        NSQd Host
        In-Flight
        Ready Count
        Finished
        Requeued
        Messages
        Connected

{{range .ChannelStats.Clients}}

        {{.ClientID}}
        {{.Version}} {{if .HasUserAgent}}({{.UserAgent}}){{end}}

          {{if .HasSampleRate}}
          Sampled {{.SampleRate}}%
          {{end}}
          {{if .TLS}}
          TLS
          {{end}}
          {{if .Deflate}}
          Delfate
          {{end}}
          {{if .Snappy}}
          Snappy
          {{end}}
          {{if .Authed}}
          {{if .AuthIdentityUrl}}{{end}}{{if .AuthIdentityUrl}}{{end}}
          {{end}}

        {{.HostAddress}}
        {{.InFlightCount | commafy}}
        {{.ReadyCount | commafy}}
        {{.FinishCount | commafy}}
        {{.RequeueCount | commafy}}
        {{.MessageCount | commafy}}
        {{.ConnectedDuration}}

{{end}}

{{end}}

{{end}}

{{template "js.html" .}}
{{template "footer.html" .}}
`)
}
<commit_msg>smaller font for useragent<commit_after>package templates

func init() {
	registerTemplate("channel.html", `
{{template "header.html" .}}
{{$g := .GraphOptions}}
{{$firstHost := .FirstHost}}

Streams /

{{.Topic}} /

{{.Channel}}

Topic: {{.Topic}}

Channel: {{.Channel}}

{{if not .ChannelStats}}

Notice
 No producers exist for this topic/channel.

See Lookup for more information.

{{else}}

        <form action="/empty_channel" method="POST">
            <input type="hidden" name="topic" value="{{.ChannelStats.TopicName}}">
            <input type="hidden" name="channel" value="{{.ChannelStats.ChannelName}}">
            <button class="btn btn-medium btn-warning" type="submit">Empty Queue</button>
        </form>

        <form action="/delete_channel" method="POST">
            <input type="hidden" name="topic" value="{{.ChannelStats.TopicName}}">
            <input type="hidden" name="channel" value="{{.ChannelStats.ChannelName}}">
            <button class="btn btn-medium btn-danger" type="submit">Delete Channel</button>
        </form>

        {{if .ChannelStats.Paused}}
        <form action="/unpause_channel" method="POST">
            <input type="hidden" name="topic" value="{{.ChannelStats.TopicName}}">
            <input type="hidden" name="channel" value="{{.ChannelStats.ChannelName}}">
            <button class="btn btn-medium btn-success" type="submit">UnPause Channel</button>
        </form>
        {{else}}
        <form action="/pause_channel" method="POST">
            <input type="hidden" name="topic" value="{{.ChannelStats.TopicName}}">
            <input type="hidden" name="channel" value="{{.ChannelStats.ChannelName}}">
            <button class="btn btn-medium btn-inverse" type="submit">Pause Channel</button>
        </form>
        {{end}}

Channel

            Message Queues
            Statistics
            {{if $firstHost.E2eProcessingLatency.Percentiles}}
              E2E Processing Latency
            {{end}}

            NSQd Host
            Depth
            Memory + Disk
            In-Flight
            Deferred
            Requeued
            Timed Out
            Messages
            {{if $g.Enabled}}Rate	{{end}}
            Connections
            {{range $e2e := $firstHost.E2eProcessingLatency.Percentiles}}
              {{$e2e.quantile | floatToPercent}}{{$e2e.quantile | percSuffix}}
            {{end}}

{{range $c := .ChannelStats.HostStats}}

            {{$c.HostAddress}}{{if $c.Paused}} paused{{end}}
            {{$c.Depth | commafy}}
            {{$c.MemoryDepth | commafy}} + {{$c.BackendDepth | commafy}}
            {{$c.InFlightCount | commafy}}
            {{$c.DeferredCount | commafy}}
            {{$c.RequeueCount | commafy}}
            {{$c.TimeoutCount | commafy}}
            {{$c.MessageCount | commafy}}
            {{if $g.Enabled}}	 {{end}}
            {{$c.ClientCount}}
            {{if $c.E2eProcessingLatency.Percentiles}}
              {{range $e2e := $c.E2eProcessingLatency.Percentiles}}

                  {{$e2e.average | nanotohuman}}

              {{end}}
            {{end}}

        {{if $g.Enabled}}

            {{if $c.E2eProcessingLatency.Percentiles}}

            {{end}}

        {{end}}

{{ end }}
{{ with $c := .ChannelStats }}

            Total:
            {{$c.Depth | commafy}}
            {{$c.MemoryDepth | commafy}} + {{$c.BackendDepth | commafy}}
            {{$c.InFlightCount | commafy}}
            {{$c.DeferredCount | commafy}}
            {{$c.RequeueCount | commafy}}
            {{$c.TimeoutCount | commafy}}
            {{$c.MessageCount | commafy}}
            {{if $g.Enabled}}	 {{end}}
            {{$c.ClientCount}}
            {{if $c.E2eProcessingLatency.Percentiles}}
              {{range $e2e := $c.E2eProcessingLatency.Percentiles}}

                  {{$e2e.average | nanotohuman}}

              {{end}}
            {{end}}

        {{if $g.Enabled}}

            {{if $c.E2eProcessingLatency.Percentiles}}

            {{end}}

        {{end}}

{{ end }}

Client Connections

{{if not .ChannelStats.Clients}}

Notice
No clients connected to this channel

{{else}}

        Client Host
        Protocol
        Attributes
        NSQd Host
        In-Flight
        Ready Count
        Finished
        Requeued
        Messages
        Connected

{{range .ChannelStats.Clients}}

        {{.ClientID}}
        {{.Version}} {{if .HasUserAgent}}({{.UserAgent}}){{end}}

          {{if .HasSampleRate}}
          Sampled {{.SampleRate}}%
          {{end}}
          {{if .TLS}}
          TLS
          {{end}}
          {{if .Deflate}}
          Delfate
          {{end}}
          {{if .Snappy}}
          Snappy
          {{end}}
          {{if .Authed}}
          {{if .AuthIdentityUrl}}{{end}}{{if .AuthIdentityUrl}}{{end}}
          {{end}}

        {{.HostAddress}}
        {{.InFlightCount | commafy}}
        {{.ReadyCount | commafy}}
        {{.FinishCount | commafy}}
        {{.RequeueCount | commafy}}
        {{.MessageCount | commafy}}
        {{.ConnectedDuration}}

{{end}}

{{end}}

{{end}}

{{template "js.html" .}}
{{template "footer.html" .}}
`)
}
<|endoftext|>


<commit_before>package edit

import (
	"bufio"
	"io"
	"os"
	"sync"

	"github.com/elves/elvish/pkg/cli"
	"github.com/elves/elvish/pkg/cli/addons/instant"
	"github.com/elves/elvish/pkg/eval"
	"github.com/elves/elvish/pkg/eval/vals"
	"github.com/elves/elvish/pkg/parse"
	"github.com/elves/elvish/pkg/strutil"
)

//elvdoc:var -instant:binding
//
// Binding for the instant mode.

//elvdoc:fn -instant:start
//
// Starts the instant mode. In instant mode, any text entered at the command
// line is evaluated immediately, with the output displayed.
//
// **WARNING**: Beware of unintended consequences when using destructive
// commands. For example, if you type `sudo rm -rf /tmp/*` in the instant mode,
// Elvish will attempt to evaluate `sudo rm -rf /` when you typed that far.

func initInstant(ed *Editor, ev *eval.Evaler, nb eval.NsBuilder) {
	bindingVar := newBindingVar(EmptyBindingMap)
	binding := newMapBinding(ed, ev, bindingVar)
	nb.AddNs("-instant",
		eval.NsBuilder{
			"binding": bindingVar,
		}.AddGoFns("<edit:-instant>:", map[string]interface{}{
			"start": func() { instantStart(ed.app, ev, binding) },
		}).Ns())
}

func instantStart(app cli.App, ev *eval.Evaler, binding cli.Handler) {
	execute := func(code string) ([]string, error) {
		src := parse.Source{Name: "[instant]", Code: code}
		op, err := ev.ParseAndCompile(src, nil)
		if err != nil {
			return nil, err
		}
		fm := eval.NewTopFrame(ev, src, []*eval.Port{
			{File: eval.DevNull},
			{}, // Will be replaced in CaptureOutput
			{File: eval.DevNull},
		})
		var output []string
		var outputMutex sync.Mutex
		addLine := func(line string) {
			outputMutex.Lock()
			defer outputMutex.Unlock()
			output = append(output, line)
		}
		valuesCb := func(ch <-chan interface{}) {
			for v := range ch {
				addLine("▶ " + vals.ToString(v))
			}
		}
		bytesCb := func(r *os.File) {
			bufr := bufio.NewReader(r)
			for {
				line, err := bufr.ReadString('\n')
				if err != nil {
					if err != io.EOF {
						addLine("i/o error: " + err.Error())
					}
					break
				}
				addLine(strutil.ChopLineEnding(line))
			}
		}
		err = fm.PipeOutput(
			func(fm *eval.Frame) error { return fm.Eval(op) }, valuesCb, bytesCb)
		return output, err
	}
	instant.Start(app, instant.Config{Binding: binding, Execute: execute})
}
<commit_msg>pkg/edit: In instant mode, use (*Evaler).Eval to evaluate the code.<commit_after>package edit

import (
	"bufio"
	"io"
	"os"
	"sync"

	"github.com/elves/elvish/pkg/cli"
	"github.com/elves/elvish/pkg/cli/addons/instant"
	"github.com/elves/elvish/pkg/eval"
	"github.com/elves/elvish/pkg/eval/vals"
	"github.com/elves/elvish/pkg/parse"
	"github.com/elves/elvish/pkg/strutil"
)

//elvdoc:var -instant:binding
//
// Binding for the instant mode.

//elvdoc:fn -instant:start
//
// Starts the instant mode. In instant mode, any text entered at the command
// line is evaluated immediately, with the output displayed.
//
// **WARNING**: Beware of unintended consequences when using destructive
// commands. For example, if you type `sudo rm -rf /tmp/*` in the instant mode,
// Elvish will attempt to evaluate `sudo rm -rf /` when you typed that far.

func initInstant(ed *Editor, ev *eval.Evaler, nb eval.NsBuilder) {
	bindingVar := newBindingVar(EmptyBindingMap)
	binding := newMapBinding(ed, ev, bindingVar)
	nb.AddNs("-instant",
		eval.NsBuilder{
			"binding": bindingVar,
		}.AddGoFns("<edit:-instant>:", map[string]interface{}{
			"start": func() { instantStart(ed.app, ev, binding) },
		}).Ns())
}

func instantStart(app cli.App, ev *eval.Evaler, binding cli.Handler) {
	execute := func(code string) ([]string, error) {
		outPort, collect, err := capturePort()
		if err != nil {
			return nil, err
		}
		err = ev.Eval(
			parse.Source{Name: "[instant]", Code: code},
			eval.EvalCfg{
				Ports:     []*eval.Port{nil, outPort},
				Interrupt: eval.ListenInterrupts})
		return collect(), err
	}
	instant.Start(app, instant.Config{Binding: binding, Execute: execute})
}

// Like eval.CapturePort, but collects output into strings, with value outputs
// stringified and prepended a marker.
func capturePort() (*eval.Port, func() []string, error) {
	var lines []string
	var mu sync.Mutex
	addLine := func(line string) {
		mu.Lock()
		defer mu.Unlock()
		lines = append(lines, line)
	}
	port, done, err := eval.PipePort(
		func(ch <-chan interface{}) {
			for v := range ch {
				addLine("▶ " + vals.ToString(v))
			}
		},
		func(r *os.File) {
			bufr := bufio.NewReader(r)
			for {
				line, err := bufr.ReadString('\n')
				if err != nil {
					if err != io.EOF {
						addLine("i/o error: " + err.Error())
					}
					break
				}
				addLine(strutil.ChopLineEnding(line))
			}
		})
	if err != nil {
		return nil, nil, err
	}
	return port, func() []string {
		done()
		return lines
	}, nil
}
<|endoftext|>


<commit_before>package env

import (
	. "github.com/smartystreets/goconvey/convey"
	"gopkg.in/inconshreveable/log15.v2"
	golog "log"
	"testing"
)

type testHandler struct {
	record *log15.Record
}

func (t *testHandler) Log(r *log15.Record) error {
	t.record = r
	return nil
}

func TestLogPkgIsBridged(t *testing.T) {
	Convey("Given a new environment", t, func() {
		handler := &testHandler{}
		Log.SetHandler(handler)

		Convey("When a log message is created using the go log pkg", func() {
			msg := "Log message"
			golog.Print(msg)

			Convey("The record should be received by the log15 handler", func() {
				So(handler.record, ShouldNotBeNil)
			})

			Convey("The record lvl should be Info", func() {
				So(handler.record.Lvl, ShouldEqual, log15.LvlInfo)
			})

			Convey("The actual message should be present", func() {
				var actual string
				for i, c := range handler.record.Ctx {
					if key, ok := c.(string); !ok {
						continue
					} else if key == "message" {
						actual = handler.record.Ctx[i+1].(string)
					}
				}
				So(actual, ShouldNotBeBlank)
				So(actual, ShouldContainSubstring, msg)
			})
		})
	})
}

type TestStringer string

func (t TestStringer) String() string {
	return string(t)
}

func TestDaemonLogFmt(t *testing.T) {
	Convey("Given a handler with the DaemonLog format", t, func() {
		handler := &testHandler{}
		Log.SetHandler(handler)

		Convey("Given a log message with a log level", func() {
			Convey("When logging a log level Crit", func() {
				msg := "crit message"
				Log.Crit(msg)

				Convey("The log message should be prefixed with SD_CRIT", func() {
					logStr := string(DaemonFormat().Format(handler.record))
					So(logStr, ShouldStartWith, sdCrit)
				})
			})
			Convey("When logging a log level Error", func() {
				msg := "error message"
				Log.Error(msg)

				Convey("The log message should be prefixed with SD_ERR", func() {
					logStr := string(DaemonFormat().Format(handler.record))
					So(logStr, ShouldStartWith, sdErr)
				})
			})
			Convey("When logging a log level Warn", func() {
				msg := "warn message"
				Log.Warn(msg)

				Convey("The log message should be prefixed with SD_WARNING", func() {
					logStr := string(DaemonFormat().Format(handler.record))
					So(logStr, ShouldStartWith, sdWarning)
				})
			})
			Convey("When logging a log level Info", func() {
				msg := "info message"
				Log.Info(msg)

				Convey("The log message should be prefixed with SD_INFO", func() {
					logStr := string(DaemonFormat().Format(handler.record))
					So(logStr, ShouldStartWith, sdInfo)
				})
			})
			Convey("When logging a log level Debug", func() {
				msg := "debug message"
				Log.Debug(msg)

				Convey("The log message should be prefixed with SD_DEBUG", func() {
					logStr := string(DaemonFormat().Format(handler.record))
					So(logStr, ShouldStartWith, sdDebug)
				})
			})
		})

		Convey("When logging a complex string", func() {
			str := "this\\should\tbe\r\nescaped\""
			Log.Debug("escape", log15.Ctx{"this": str})

			Convey("The log message should be properly escaped", func() {
				expect := "this=\"this\\\\should\\tbe\\r\\nescaped\\\"\""
				logStr := string(DaemonFormat().Format(handler.record))
				So(logStr, ShouldContainSubstring, expect)
			})
		})

		Convey("When logging a complex Stringer", func() {
			str := TestStringer("this\\should\tbe\r\nescaped\"")
			Log.Debug("escape", log15.Ctx{"this": str})

			Convey("The log message should be properly escaped", func() {
				expect := "this=\"this\\\\should\\tbe\\r\\nescaped\\\"\""
				logStr := string(DaemonFormat().Format(handler.record))
				So(logStr, ShouldContainSubstring, expect)
			})
		})
	})
}
<commit_msg>requirement: errors should be escaped<commit_after>package env

import (
	"errors"
	. "github.com/smartystreets/goconvey/convey"
	"gopkg.in/inconshreveable/log15.v2"
	golog "log"
	"testing"
)

type testHandler struct {
	record *log15.Record
}

func (t *testHandler) Log(r *log15.Record) error {
	t.record = r
	return nil
}

func TestLogPkgIsBridged(t *testing.T) {
	Convey("Given a new environment", t, func() {
		handler := &testHandler{}
		Log.SetHandler(handler)

		Convey("When a log message is created using the go log pkg", func() {
			msg := "Log message"
			golog.Print(msg)

			Convey("The record should be received by the log15 handler", func() {
				So(handler.record, ShouldNotBeNil)
			})

			Convey("The record lvl should be Info", func() {
				So(handler.record.Lvl, ShouldEqual, log15.LvlInfo)
			})

			Convey("The actual message should be present", func() {
				var actual string
				for i, c := range handler.record.Ctx {
					if key, ok := c.(string); !ok {
						continue
					} else if key == "message" {
						actual = handler.record.Ctx[i+1].(string)
					}
				}
				So(actual, ShouldNotBeBlank)
				So(actual, ShouldContainSubstring, msg)
			})
		})
	})
}

type TestStringer string

func (t TestStringer) String() string {
	return string(t)
}

func TestDaemonLogFmt(t *testing.T) {
	Convey("Given a handler with the DaemonLog format", t, func() {
		handler := &testHandler{}
		Log.SetHandler(handler)

		Convey("Given a log message with a log level", func() {
			Convey("When logging a log level Crit", func() {
				msg := "crit message"
				Log.Crit(msg)

				Convey("The log message should be prefixed with SD_CRIT", func() {
					logStr := string(DaemonFormat().Format(handler.record))
					So(logStr, ShouldStartWith, sdCrit)
				})
			})
			Convey("When logging a log level Error", func() {
				msg := "error message"
				Log.Error(msg)

				Convey("The log message should be prefixed with SD_ERR", func() {
					logStr := string(DaemonFormat().Format(handler.record))
					So(logStr, ShouldStartWith, sdErr)
				})
			})
			Convey("When logging a log level Warn", func() {
				msg := "warn message"
				Log.Warn(msg)

				Convey("The log message should be prefixed with SD_WARNING", func() {
					logStr := string(DaemonFormat().Format(handler.record))
					So(logStr, ShouldStartWith, sdWarning)
				})
			})
			Convey("When logging a log level Info", func() {
				msg := "info message"
				Log.Info(msg)

				Convey("The log message should be prefixed with SD_INFO", func() {
					logStr := string(DaemonFormat().Format(handler.record))
					So(logStr, ShouldStartWith, sdInfo)
				})
			})
			Convey("When logging a log level Debug", func() {
				msg := "debug message"
				Log.Debug(msg)

				Convey("The log message should be prefixed with SD_DEBUG", func() {
					logStr := string(DaemonFormat().Format(handler.record))
					So(logStr, ShouldStartWith, sdDebug)
				})
			})
		})

		Convey("When logging a complex string", func() {
			str := "this\\should\tbe\r\nescaped\""
			Log.Debug("escape", log15.Ctx{"this": str})

			Convey("The log message should be properly escaped", func() {
				expect := "this=\"this\\\\should\\tbe\\r\\nescaped\\\"\""
				logStr := string(DaemonFormat().Format(handler.record))
				So(logStr, ShouldContainSubstring, expect)
			})
		})

		Convey("When logging a complex Stringer", func() {
			str := TestStringer("this\\should\tbe\r\nescaped\"")
			Log.Debug("escape", log15.Ctx{"this": str})

			Convey("The log message should be properly escaped", func() {
				expect := "this=\"this\\\\should\\tbe\\r\\nescaped\\\"\""
				logStr := string(DaemonFormat().Format(handler.record))
				So(logStr, ShouldContainSubstring, expect)
			})
		})

		Convey("When logging a complex error", func() {
			err := errors.New("this\\should\tbe\r\nescaped\"")
			Log.Debug("escape", log15.Ctx{"err": err})

			Convey("The log message should be properly escaped", func() {
				expect := "this=\"this\\\\should\\tbe\\r\\nescaped\\\"\""
				logStr := string(DaemonFormat().Format(handler.record))
				So(logStr, ShouldContainSubstring, expect)
			})
		})
	})
}
<|endoftext|>