<commit_before>package mockingjay

import (
	"errors"
	"fmt"
	"gopkg.in/yaml.v2"
)

type response struct {
	Code    int
	Body    string
	Headers map[string]string
}

func (r response) isValid() bool {
	return r.Code != 0
}

// FakeEndpoint represents the information required to listen to a particular request and respond to it
type FakeEndpoint struct {
	Name        string //A description of what this endpoint is.
	CDCDisabled bool   // When set to true it will not be included in the consumer driven contract tests against real server
	Request     Request
	Response    response
}

func (f *FakeEndpoint) String() string {
	return fmt.Sprintf(fakeEndpointStringerFormat, f.Name, f.Request)
}

const fakeEndpointStringerFormat = "%s (%s)"

// NewFakeEndpoints returns an array of Endpoints from a YAML byte array. Returns an error if YAML cannot be parsed
func NewFakeEndpoints(data []byte) (endpoints []FakeEndpoint, err error) {
	err = yaml.Unmarshal(data, &endpoints)

	if err != nil {
		return
	}

	for _, endPoint := range endpoints {
		if !endPoint.isValid() {
			err = errors.New("config YAML structure is invalid")
			return
		}
	}

	return
}

func (f FakeEndpoint) isValid() bool {
	return f.Request.isValid() && f.Response.isValid()
}

type notFoundResponse struct {
	Message            string
	Request            Request        `json:"Request received"`
	EndpointsAvailable []FakeEndpoint `json:"Endpoints available"`
}

func newNotFound(method string, url string, body string, headers map[string]string, endpoints []FakeEndpoint) *response {
	notFound := notFoundResponse{"Endpoint not found", Request{url, method, headers, body}, endpoints}
	j, _ := yaml.Marshal(notFound)
	return &response{404, string(j), nil}
}
<commit_msg>Not found endpoint now returning JSON for upcoming pretty UI<commit_after>package mockingjay

import (
	"encoding/json"
	"errors"
	"fmt"
	"gopkg.in/yaml.v2"
)

type response struct {
	Code    int
	Body    string
	Headers map[string]string
}

func (r response) isValid() bool {
	return r.Code != 0
}

// FakeEndpoint represents the information required to listen to a particular request and respond to it
type FakeEndpoint struct {
	Name        string //A description of what this endpoint is.
	CDCDisabled bool   // When set to true it will not be included in the consumer driven contract tests against real server
	Request     Request
	Response    response
}

func (f *FakeEndpoint) String() string {
	return fmt.Sprintf(fakeEndpointStringerFormat, f.Name, f.Request)
}

const fakeEndpointStringerFormat = "%s (%s)"

// NewFakeEndpoints returns an array of Endpoints from a YAML byte array. Returns an error if YAML cannot be parsed
func NewFakeEndpoints(data []byte) (endpoints []FakeEndpoint, err error) {
	err = yaml.Unmarshal(data, &endpoints)

	if err != nil {
		return
	}

	for _, endPoint := range endpoints {
		if !endPoint.isValid() {
			err = errors.New("config YAML structure is invalid")
			return
		}
	}

	return
}

func (f FakeEndpoint) isValid() bool {
	return f.Request.isValid() && f.Response.isValid()
}

type notFoundResponse struct {
	Message            string
	Request            Request        `json:"RequestReceived"`
	EndpointsAvailable []FakeEndpoint `json:"EndpointsAvailable"`
}

func newNotFound(method string, url string, body string, headers map[string]string, endpoints []FakeEndpoint) *response {
	notFound := notFoundResponse{"Endpoint not found", Request{url, method, headers, body}, endpoints}
	j, _ := json.Marshal(notFound)
	return &response{404, string(j), nil}
}
<|endoftext|>


<commit_before>// 24 september 2014

package main

import (
	"path/filepath"
	"strings"
	"sort"
)

type Stage []*Executor

func (s Stage) Len() int {
	return len(s)
}

func (s Stage) Less(i, j int) bool {
	return s[i].Line[1] < s[j].Line[1]
}

func (s Stage) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

var script []Stage
var nStages int

func buildScript() {
	script = nil
	nStages = 0

	// stage 1: make the object file directory
	e := &Executor{
		Name:	"Made working directory",
		Line:		[]string{"mkdir", "-p", ".qoobj"},
	}
	script = append(script, Stage{e})
	nStages++

	// stage 2: compile everything
	stage2 := Stage(nil)
	objects := []string(nil)
	for _, f := range cfiles {
		object := strings.Replace(f, string(filepath.Separator), "_", -1) + ".o"
		object = filepath.Join(".qoobj", object)
		e = &Executor{
			Name:	"Compiled " + f,
			Line:		[]string{toolchain.CC, f, "-c", "-o", object},
		}
		e.Line = append(e.Line, toolchain.CFLAGS...)
		stage2 = append(stage2, e)
		objects = append(objects, object)
	}
	sort.Sort(stage2)
	sort.Strings(objects)
	script = append(script, stage2)
	nStages += len(stage2)

	// 3) link
	target := targetName()
	e = &Executor{
		Name:	"Linked " + target,
		Line:		make([]string, 0, len(objects) + 10),
	}
	e.Line = append(e.Line, "gcc", "-o", target)
	e.Line = append(e.Line, objects...)
	script = append(script, Stage{e})
	nStages++
}
<commit_msg>Added LDFLAGS.<commit_after>// 24 september 2014

package main

import (
	"path/filepath"
	"strings"
	"sort"
)

type Stage []*Executor

func (s Stage) Len() int {
	return len(s)
}

func (s Stage) Less(i, j int) bool {
	return s[i].Line[1] < s[j].Line[1]
}

func (s Stage) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

var script []Stage
var nStages int

func buildScript() {
	script = nil
	nStages = 0

	// stage 1: make the object file directory
	e := &Executor{
		Name:	"Made working directory",
		Line:		[]string{"mkdir", "-p", ".qoobj"},
	}
	script = append(script, Stage{e})
	nStages++

	// stage 2: compile everything
	stage2 := Stage(nil)
	objects := []string(nil)
	for _, f := range cfiles {
		object := strings.Replace(f, string(filepath.Separator), "_", -1) + ".o"
		object = filepath.Join(".qoobj", object)
		e = &Executor{
			Name:	"Compiled " + f,
			Line:		[]string{toolchain.CC, f, "-c", "-o", object},
		}
		e.Line = append(e.Line, toolchain.CFLAGS...)
		stage2 = append(stage2, e)
		objects = append(objects, object)
	}
	sort.Sort(stage2)
	sort.Strings(objects)
	script = append(script, stage2)
	nStages += len(stage2)

	// 3) link
	target := targetName()
	e = &Executor{
		Name:	"Linked " + target,
		Line:		make([]string, 0, len(objects) + len(toolchain.LDFLAGS) + 10),
	}
	e.Line = append(e.Line, "gcc", "-o", target)
	e.Line = append(e.Line, objects...)
	e.Line = append(e.Line, toolchain.LDFLAGS...)
	script = append(script, Stage{e})
	nStages++
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/flowbase/flowbase"
	"testing"
)

// TestNewResouceIndexCreator tests NewResourceIndexCreator
func TestNewResourceIndexCreator(t *testing.T) {
	flowbase.InitLogWarning()

	ric := NewResourceIndexCreator()

	if ric.In == nil {
		t.Error("In-port not initialized")
	}
	if ric.Out == nil {
		t.Error("Out-port not initialized")
	}
}
<commit_msg>Add tests for ResourceIndexCreator<commit_after>package main

import (
	"fmt"
	"github.com/flowbase/flowbase"
	"github.com/knakk/rdf"
	"testing"
)

// TestNewResouceIndexCreator tests NewResourceIndexCreator
func TestNewResourceIndexCreator(t *testing.T) {
	flowbase.InitLogWarning()

	ric := NewResourceIndexCreator()

	if ric.In == nil {
		t.Error("In-port not initialized")
	}
	if ric.Out == nil {
		t.Error("Out-port not initialized")
	}
}

func TestResourceIndexCreator(t *testing.T) {
	flowbase.InitLogWarning()

	ric := NewResourceIndexCreator()

	var triples = []rdf.Triple{}

	go func() {
		defer close(ric.In)

		for i := 1; i <= 2; i++ {

			triples = []rdf.Triple{}
			s, serr := rdf.NewIRI(fmt.Sprintf("http://example.org/s%d", i))
			if serr != nil {
				t.Error("Could not create Subject IRI")
			}
			for j := 1; j <= 3; j++ {
				p, perr := rdf.NewIRI(fmt.Sprintf("http://example.org/p%d", j))
				if perr != nil {
					t.Error("Could not create Predicate IRI")
				}
				o, oerr := rdf.NewLiteral(fmt.Sprintf("o%d", j))
				if oerr != nil {
					t.Error("Could not create Object Literal")
				}
				tr := rdf.Triple{
					Subj: s,
					Pred: p,
					Obj:  o,
				}
				triples = append(triples, tr)
			}

			aggr := NewTripleAggregate(s, triples)
			ric.In <- aggr
		}
	}()

	go ric.Run()

	resIdx := <-ric.Out

	if (*resIdx)["http://example.org/s1"] == nil {
		t.Error("Resource index does not contain first subject")
	}

	if (*resIdx)["http://example.org/s1"].Subject.String() != "http://example.org/s1" {
		t.Error("Subject string in first subject is wrong")
	}

	if len((*resIdx)["http://example.org/s1"].Triples) != 3 {
		t.Error("Wrong number of triples for first subject")
	}

	if (*resIdx)["http://example.org/s2"] == nil {
		t.Error("Resource index does not contain second subject")
	}

	if (*resIdx)["http://example.org/s2"].Subject.String() != "http://example.org/s2" {
		t.Error("Subject string in second subject is wrong")
	}

	if len((*resIdx)["http://example.org/s2"].Triples) != 3 {
		t.Error("Wrong number of triples for second subject")
	}
}
<|endoftext|>


<commit_before>package workflows

import (
	"bytes"
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"
	"time"

	"github.com/go-kit/kit/log"
	"github.com/metalmatze/alertmanager-bot/pkg/telegram"
	"github.com/prometheus/alertmanager/notify"
	"github.com/stretchr/testify/require"
	"github.com/tucnak/telebot"
)

var (
	admin = telebot.User{
		ID:        123,
		FirstName: "Elliot",
		LastName:  "Alderson",
		Username:  "elliot",
		IsBot:     false,
	}
	nobody = telebot.User{
		ID:        222,
		FirstName: "John",
		LastName:  "Doe",
		Username:  "nobody",
		IsBot:     false,
	}

	// These are the different workflows/scenarios we are testing.
	workflows = []struct {
		name     string
		messages []telebot.Message
		replies  []testTelegramReply
		logs     []string
	}{{
		name: "Dropped",
		messages: []telebot.Message{{
			Sender: nobody,
		}},
		replies: []testTelegramReply{},
		logs: []string{
			"level=info msg=\"failed to process message\" err=\"dropped message from forbidden sender\" sender_id=222 sender_username=nobody",
		},
	}, {
		name: "Incomprehensible",
		messages: []telebot.Message{{
			Sender: admin,
			Chat:   chatFromUser(admin),
			Text:   "/incomprehensible",
		}},
		replies: []testTelegramReply{{
			recipient: "123",
			message:   "Sorry, I don't understand...",
		}},
		logs: []string{
			"level=debug msg=\"message received\" text=/incomprehensible",
		},
	}, {
		name: "Start",
		messages: []telebot.Message{{
			Sender: admin,
			Chat:   chatFromUser(admin),
			Text:   telegram.CommandStart,
		}},
		replies: []testTelegramReply{{
			recipient: "123",
			message:   "Hey, Elliot! I will now keep you up to date!\n/help",
		}},
		logs: []string{
			"level=debug msg=\"message received\" text=/start",
			"level=info msg=\"user subscribed\" username=elliot user_id=123",
		},
	}, {
		name: "StopWithoutStart",
		messages: []telebot.Message{{
			Sender: admin,
			Chat:   chatFromUser(admin),
			Text:   telegram.CommandStop,
		}},
		replies: []testTelegramReply{{
			recipient: "123",
			message:   "Alright, Elliot! I won't talk to you again.\n/help",
		}},
		logs: []string{
			"level=debug msg=\"message received\" text=/stop",
			"level=info msg=\"user unsubscribed\" username=elliot user_id=123",
		},
	}, {
		name: "Help",
		messages: []telebot.Message{{
			Sender: admin,
			Chat:   chatFromUser(admin),
			Text:   telegram.CommandHelp,
		}},
		replies: []testTelegramReply{{
			recipient: "123",
			message:   telegram.ResponseHelp,
		}},
		logs: []string{
			"level=debug msg=\"message received\" text=/help",
		},
	}, {
		name: "HelpAsNobody",
		messages: []telebot.Message{{
			Sender: nobody,
			Chat:   chatFromUser(nobody),
			Text:   telegram.CommandHelp,
		}},
		replies: []testTelegramReply{},
		logs: []string{
			"level=info msg=\"failed to process message\" err=\"dropped message from forbidden sender\" sender_id=222 sender_username=nobody",
		},
	}, {
		name: "ChatsNone",
		messages: []telebot.Message{{
			Sender: admin,
			Chat:   chatFromUser(admin),
			Text:   telegram.CommandChats,
		}},
		replies: []testTelegramReply{{
			recipient: "123",
			message:   "Currently no one is subscribed.",
		}},
		logs: []string{
			"level=debug msg=\"message received\" text=/chats",
		},
	}, {
		name: "ChatsWithAdminSubscribed",
		messages: []telebot.Message{{
			Sender: admin,
			Chat:   chatFromUser(admin),
			Text:   telegram.CommandStart,
		}, {
			Sender: admin,
			Chat:   chatFromUser(admin),
			Text:   telegram.CommandChats,
		}},
		replies: []testTelegramReply{{
			recipient: "123",
			message:   "Hey, Elliot! I will now keep you up to date!\n/help",
		}, {
			recipient: "123",
			message:   "Currently these chat have subscribed:\n@elliot\n",
		}},
		logs: []string{
			"level=debug msg=\"message received\" text=/start",
			"level=info msg=\"user subscribed\" username=elliot user_id=123",
			"level=debug msg=\"message received\" text=/chats",
		},
	}, {
		name: "Status",
		messages: []telebot.Message{{
			Sender: admin,
			Chat:   chatFromUser(admin),
			Text:   telegram.CommandStatus,
		}},
		replies: []testTelegramReply{{
			recipient: "123",
			message:   "*AlertManager*\nVersion: alertmanager\nUptime: 1 minute\n*AlertManager Bot*\nVersion: bot\nUptime: 1 minute",
		}},
		logs: []string{
			"level=debug msg=\"message received\" text=/status",
		},
	}}
)

func chatFromUser(user telebot.User) telebot.Chat {
	return telebot.Chat{
		ID:        int64(user.ID),
		FirstName: user.FirstName,
		LastName:  user.LastName,
		Username:  user.Username,
		Type:      telebot.ChatPrivate,
	}
}

type testStore struct {
	// not thread safe - lol
	chats map[int64]telebot.Chat
}

func (t *testStore) List() ([]telebot.Chat, error) {
	chats := make([]telebot.Chat, 0, len(t.chats))
	for _, chat := range t.chats {
		chats = append(chats, chat)
	}
	return chats, nil
}

func (t *testStore) Add(c telebot.Chat) error {
	if t.chats == nil {
		t.chats = make(map[int64]telebot.Chat)
	}
	t.chats[c.ID] = c
	return nil
}

func (t *testStore) Remove(_ telebot.Chat) error {
	return nil
}

type testTelegramReply struct {
	recipient, message string
}

type testTelegram struct {
	messages []telebot.Message
	replies  []testTelegramReply
}

func (t *testTelegram) Listen(messages chan telebot.Message, _ time.Duration) {
	for i, m := range t.messages {
		m.ID = i
		messages <- m
	}
}

func (t *testTelegram) SendChatAction(_ telebot.Recipient, _ telebot.ChatAction) error {
	return nil
}

func (t *testTelegram) SendMessage(recipient telebot.Recipient, message string, _ *telebot.SendOptions) error {
	t.replies = append(t.replies, testTelegramReply{recipient: recipient.Destination(), message: message})
	return nil
}

func TestWorkflows(t *testing.T) {
	var testAlertmanagerURL *url.URL
	{
		m := http.NewServeMux()
		m.HandleFunc("/api/v1/status", func(w http.ResponseWriter, r *http.Request) {
			data := fmt.Sprintf(
				`{"data":{"uptime":%q,"versionInfo":{"version":"alertmanager"}}}"`,
				time.Now().Add(-time.Minute).Format(time.RFC3339),
			)
			_, _ = w.Write([]byte(data))
		})

		server := httptest.NewServer(m)
		defer server.Close()

		testAlertmanagerURL, _ = url.Parse(server.URL)
	}

	for _, w := range workflows {
		t.Run(w.name, func(t *testing.T) {
			ctx, cancel := context.WithCancel(context.Background())
			logs := &bytes.Buffer{}

			testStore := &testStore{}
			testTelegram := &testTelegram{messages: w.messages}

			bot, err := telegram.NewBotWithTelegram(testStore, testTelegram, admin.ID,
				telegram.WithLogger(log.NewLogfmtLogger(logs)),
				telegram.WithAlertmanager(testAlertmanagerURL),
				telegram.WithStartTime(time.Now().Add(-time.Minute)),
				telegram.WithRevision("bot"),
			)
			require.NoError(t, err)

			// Run the bot in the background and tests in foreground.
			go func(ctx context.Context) {
				err = bot.Run(ctx, make(chan notify.WebhookMessage))
				require.NoError(t, err)
			}(ctx)

			// TODO: Don't sleep but block somehow different
			time.Sleep(100 * time.Millisecond)

			require.Len(t, testTelegram.replies, len(w.replies))
			for i, reply := range w.replies {
				require.Equal(t, reply.recipient, testTelegram.replies[i].recipient)
				require.Equal(t, reply.message, testTelegram.replies[i].message)
			}

			logLines := strings.Split(strings.TrimSpace(logs.String()), "\n")

			require.Len(t, logLines, len(w.logs))
			for i, l := range w.logs {
				require.Equal(t, l, logLines[i])
			}

			cancel()
		})
	}
}
<commit_msg>tests/workflows: Add /alerts with none,firing,resolved state<commit_after>package workflows

import (
	"bytes"
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"
	"time"

	"github.com/go-kit/kit/log"
	"github.com/metalmatze/alertmanager-bot/pkg/telegram"
	"github.com/prometheus/alertmanager/notify"
	"github.com/stretchr/testify/require"
	"github.com/tucnak/telebot"
)

var (
	admin = telebot.User{
		ID:        123,
		FirstName: "Elliot",
		LastName:  "Alderson",
		Username:  "elliot",
		IsBot:     false,
	}
	nobody = telebot.User{
		ID:        222,
		FirstName: "John",
		LastName:  "Doe",
		Username:  "nobody",
		IsBot:     false,
	}

	// These are the different workflows/scenarios we are testing.
	workflows = []struct {
		name               string
		messages           []telebot.Message
		replies            []testTelegramReply
		logs               []string
		alertmanagerAlerts func() string
		alertmanagerStatus func() string
	}{{
		name: "Dropped",
		messages: []telebot.Message{{
			Sender: nobody,
		}},
		replies: []testTelegramReply{},
		logs: []string{
			"level=info msg=\"failed to process message\" err=\"dropped message from forbidden sender\" sender_id=222 sender_username=nobody",
		},
	}, {
		name: "Incomprehensible",
		messages: []telebot.Message{{
			Sender: admin,
			Chat:   chatFromUser(admin),
			Text:   "/incomprehensible",
		}},
		replies: []testTelegramReply{{
			recipient: "123",
			message:   "Sorry, I don't understand...",
		}},
		logs: []string{
			"level=debug msg=\"message received\" text=/incomprehensible",
		},
	}, {
		name: "Start",
		messages: []telebot.Message{{
			Sender: admin,
			Chat:   chatFromUser(admin),
			Text:   telegram.CommandStart,
		}},
		replies: []testTelegramReply{{
			recipient: "123",
			message:   "Hey, Elliot! I will now keep you up to date!\n/help",
		}},
		logs: []string{
			"level=debug msg=\"message received\" text=/start",
			"level=info msg=\"user subscribed\" username=elliot user_id=123",
		},
	}, {
		name: "StopWithoutStart",
		messages: []telebot.Message{{
			Sender: admin,
			Chat:   chatFromUser(admin),
			Text:   telegram.CommandStop,
		}},
		replies: []testTelegramReply{{
			recipient: "123",
			message:   "Alright, Elliot! I won't talk to you again.\n/help",
		}},
		logs: []string{
			"level=debug msg=\"message received\" text=/stop",
			"level=info msg=\"user unsubscribed\" username=elliot user_id=123",
		},
	}, {
		name: "Help",
		messages: []telebot.Message{{
			Sender: admin,
			Chat:   chatFromUser(admin),
			Text:   telegram.CommandHelp,
		}},
		replies: []testTelegramReply{{
			recipient: "123",
			message:   strings.TrimSpace(telegram.ResponseHelp),
		}},
		logs: []string{
			"level=debug msg=\"message received\" text=/help",
		},
	}, {
		name: "HelpAsNobody",
		messages: []telebot.Message{{
			Sender: nobody,
			Chat:   chatFromUser(nobody),
			Text:   telegram.CommandHelp,
		}},
		replies: []testTelegramReply{},
		logs: []string{
			"level=info msg=\"failed to process message\" err=\"dropped message from forbidden sender\" sender_id=222 sender_username=nobody",
		},
	}, {
		name: "ChatsNone",
		messages: []telebot.Message{{
			Sender: admin,
			Chat:   chatFromUser(admin),
			Text:   telegram.CommandChats,
		}},
		replies: []testTelegramReply{{
			recipient: "123",
			message:   "Currently no one is subscribed.",
		}},
		logs: []string{
			"level=debug msg=\"message received\" text=/chats",
		},
	}, {
		name: "ChatsWithAdminSubscribed",
		messages: []telebot.Message{{
			Sender: admin,
			Chat:   chatFromUser(admin),
			Text:   telegram.CommandStart,
		}, {
			Sender: admin,
			Chat:   chatFromUser(admin),
			Text:   telegram.CommandChats,
		}},
		replies: []testTelegramReply{{
			recipient: "123",
			message:   "Hey, Elliot! I will now keep you up to date!\n/help",
		}, {
			recipient: "123",
			message:   "Currently these chat have subscribed:\n@elliot",
		}},
		logs: []string{
			"level=debug msg=\"message received\" text=/start",
			"level=info msg=\"user subscribed\" username=elliot user_id=123",
			"level=debug msg=\"message received\" text=/chats",
		},
	}, {
		name: "Status",
		messages: []telebot.Message{{
			Sender: admin,
			Chat:   chatFromUser(admin),
			Text:   telegram.CommandStatus,
		}},
		replies: []testTelegramReply{{
			recipient: "123",
			message:   "*AlertManager*\nVersion: alertmanager\nUptime: 1 minute\n*AlertManager Bot*\nVersion: bot\nUptime: 1 minute",
		}},
		logs: []string{
			"level=debug msg=\"message received\" text=/status",
		},
		alertmanagerStatus: func() string {
			return fmt.Sprintf(
				`{"data":{"uptime":%q,"versionInfo":{"version":"alertmanager"}}}"`,
				time.Now().Add(-time.Minute).Format(time.RFC3339),
			)
		},
	}, {
		name: "AlertsNone",
		messages: []telebot.Message{{
			Sender: admin,
			Chat:   chatFromUser(admin),
			Text:   telegram.CommandAlerts,
		}},
		replies: []testTelegramReply{{
			recipient: "123",
			message:   "No alerts right now! 🎉",
		}},
		logs: []string{
			"level=debug msg=\"message received\" text=/alerts",
		},
	}, {
		name: "AlertsFiring",
		messages: []telebot.Message{{
			Sender: admin,
			Chat:   chatFromUser(admin),
			Text:   telegram.CommandAlerts,
		}},
		replies: []testTelegramReply{{
			recipient: "123",
			message:   "🔥 damn 🔥\nLabels:\n    bot: alertmanager-bot\nAnnotations:\n    msg: sup?!\nDuration: 1 hour",
		}},
		logs: []string{
			"level=debug msg=\"message received\" text=/alerts",
		},
		alertmanagerAlerts: func() string {
			return fmt.Sprintf(
				`{"status":"success", "data":[{"labels":{"alertname":"damn","bot":"alertmanager-bot"},"annotations":{"msg":"sup?!"},"startsAt":%q}]}`,
				time.Now().Add(-time.Hour).Format(time.RFC3339),
			)
		},
	}, {
		name: "AlertsResolved",
		messages: []telebot.Message{{
			Sender: admin,
			Chat:   chatFromUser(admin),
			Text:   telegram.CommandAlerts,
		}},
		replies: []testTelegramReply{{
			recipient: "123",
			message:   "✅ damn ✅\nLabels:\n    bot: alertmanager-bot\nAnnotations:\n    msg: sup?!\nDuration: 58 minutes\nEnded: 2 minutes",
		}},
		logs: []string{
			"level=debug msg=\"message received\" text=/alerts",
		},
		alertmanagerAlerts: func() string {
			return fmt.Sprintf(
				`{"status":"success", "data":[{"labels":{"alertname":"damn","bot":"alertmanager-bot"},"annotations":{"msg":"sup?!"},"startsAt":%q,"endsAt":%q}]}`,
				time.Now().Add(-time.Hour).Format(time.RFC3339),
				time.Now().Add(-2*time.Minute).Format(time.RFC3339),
			)
		},
	}}
)

func chatFromUser(user telebot.User) telebot.Chat {
	return telebot.Chat{
		ID:        int64(user.ID),
		FirstName: user.FirstName,
		LastName:  user.LastName,
		Username:  user.Username,
		Type:      telebot.ChatPrivate,
	}
}

type testStore struct {
	// not thread safe - lol
	chats map[int64]telebot.Chat
}

func (t *testStore) List() ([]telebot.Chat, error) {
	chats := make([]telebot.Chat, 0, len(t.chats))
	for _, chat := range t.chats {
		chats = append(chats, chat)
	}
	return chats, nil
}

func (t *testStore) Add(c telebot.Chat) error {
	if t.chats == nil {
		t.chats = make(map[int64]telebot.Chat)
	}
	t.chats[c.ID] = c
	return nil
}

func (t *testStore) Remove(_ telebot.Chat) error {
	return nil
}

type testTelegramReply struct {
	recipient, message string
}

type testTelegram struct {
	messages []telebot.Message
	replies  []testTelegramReply
}

func (t *testTelegram) Listen(messages chan telebot.Message, _ time.Duration) {
	for i, m := range t.messages {
		m.ID = i
		messages <- m
	}
}

func (t *testTelegram) SendChatAction(_ telebot.Recipient, _ telebot.ChatAction) error {
	return nil
}

func (t *testTelegram) SendMessage(recipient telebot.Recipient, message string, _ *telebot.SendOptions) error {
	t.replies = append(t.replies, testTelegramReply{recipient: recipient.Destination(), message: message})
	return nil
}

func TestWorkflows(t *testing.T) {
	var testAlertmanagerAlerts func() string
	var testAlertmanagerStatus func() string
	var testAlertmanagerURL *url.URL
	{
		m := http.NewServeMux()
		m.HandleFunc("/api/v1/alerts", func(w http.ResponseWriter, r *http.Request) {
			data := "{}"
			if testAlertmanagerAlerts != nil {
				data = testAlertmanagerAlerts()
			}
			_, _ = w.Write([]byte(data))
		})
		m.HandleFunc("/api/v1/status", func(w http.ResponseWriter, r *http.Request) {
			data := "{}"
			if testAlertmanagerStatus != nil {
				data = testAlertmanagerStatus()
			}
			_, _ = w.Write([]byte(data))
		})

		server := httptest.NewServer(m)
		defer server.Close()

		testAlertmanagerURL, _ = url.Parse(server.URL)
	}

	for _, w := range workflows {
		t.Run(w.name, func(t *testing.T) {
			testAlertmanagerAlerts = w.alertmanagerAlerts
			testAlertmanagerStatus = w.alertmanagerStatus

			ctx, cancel := context.WithCancel(context.Background())
			logs := &bytes.Buffer{}

			testStore := &testStore{}
			testTelegram := &testTelegram{messages: w.messages}

			bot, err := telegram.NewBotWithTelegram(testStore, testTelegram, admin.ID,
				telegram.WithLogger(log.NewLogfmtLogger(logs)),
				telegram.WithAlertmanager(testAlertmanagerURL),
				telegram.WithTemplates(&url.URL{Host: "localhost"}, "../../default.tmpl"),
				telegram.WithStartTime(time.Now().Add(-time.Minute)),
				telegram.WithRevision("bot"),
			)
			require.NoError(t, err)

			// Run the bot in the background and tests in foreground.
			go func(ctx context.Context) {
				require.NoError(t, bot.Run(ctx, make(chan notify.WebhookMessage)))
			}(ctx)

			// TODO: Don't sleep but block somehow different
			time.Sleep(100 * time.Millisecond)

			require.Len(t, testTelegram.replies, len(w.replies))
			for i, reply := range w.replies {
				require.Equal(t, reply.recipient, testTelegram.replies[i].recipient)
				require.Equal(t, reply.message, strings.TrimSpace(testTelegram.replies[i].message))
			}

			logLines := strings.Split(strings.TrimSpace(logs.String()), "\n")

			require.Len(t, logLines, len(w.logs))
			for i, l := range w.logs {
				require.Equal(t, l, logLines[i])
			}

			cancel()
		})
	}
}
<|endoftext|>


<commit_before>package engine

import (
	"fmt"
	"net/http"

	"github.com/funkygao/dbus"
	"github.com/funkygao/dbus/pkg/cluster"
	"github.com/funkygao/gorequest"
	log "github.com/funkygao/log4go"
)

func (e *Engine) onControllerRebalance(epoch int, decision cluster.Decision) {
	log.Info("[%s] decision: %+v", e.participant, decision)

	for participant, resources := range decision {
		log.Trace("[%s] rpc-> %s %+v", e.participant, participant.Endpoint, resources)

		// edge case:
		// participant might die
		// participant not die, but its Input plugin panic
		// leader might die
		// rpc might be rejected
		statusCode := e.callRPC(participant.Endpoint, epoch, resources)
		switch statusCode {
		case http.StatusOK:
			log.Trace("[%s] rpc<- ok %s", e.participant, participant.Endpoint)
		case http.StatusGone:
			// will wait for another rebalance event
			log.Warn("[%s] rpc<- %s gone", e.participant, participant.Endpoint)
		case http.StatusBadRequest:
			// should never happen
			log.Critical("[%s] rpc<- %s bad request", e.participant, participant.Endpoint)
		case http.StatusNotAcceptable:
			log.Error("[%s] rpc<- %s leader moved", e.participant, participant.Endpoint)
		default:
			log.Error("[%s] rpc<- %s %d", e.participant, participant.Endpoint, statusCode)
		}

	}
}

func (e *Engine) callRPC(endpoint string, epoch int, resources []cluster.Resource) int {
	resp, _, errs := gorequest.New().
		Post(fmt.Sprintf("http://%s/v1/rebalance?epoch=%d", endpoint, epoch)).
		Set("User-Agent", fmt.Sprintf("dbus-%s", dbus.Revision)).
		SendString(string(cluster.Resources(resources).Marshal())).
		End()
	if len(errs) > 0 {
		// e,g. participant gone
		// connection reset
		// connnection refused
		// FIXME what if connection timeout?
		return http.StatusGone
	}

	return resp.StatusCode
}
<commit_msg>explains why participant gone might happen<commit_after>package engine

import (
	"fmt"
	"net/http"

	"github.com/funkygao/dbus"
	"github.com/funkygao/dbus/pkg/cluster"
	"github.com/funkygao/gorequest"
	log "github.com/funkygao/log4go"
)

func (e *Engine) onControllerRebalance(epoch int, decision cluster.Decision) {
	log.Info("[%s] decision: %+v", e.participant, decision)

	for participant, resources := range decision {
		log.Trace("[%s] rpc-> %s %+v", e.participant, participant.Endpoint, resources)

		// edge case:
		// participant might die
		// participant not die, but its Input plugin panic
		// leader might die
		// rpc might be rejected
		statusCode := e.callRPC(participant.Endpoint, epoch, resources)
		switch statusCode {
		case http.StatusOK:
			log.Trace("[%s] rpc<- ok %s", e.participant, participant.Endpoint)
		case http.StatusGone:
			// e,g.
			// resource changed, live participant [1, 2, 3], when RPC sending, p[1] gone
			// just wait for another rebalance event
			log.Warn("[%s] rpc<- %s gone", e.participant, participant.Endpoint)
		case http.StatusBadRequest:
			// should never happen
			log.Critical("[%s] rpc<- %s bad request", e.participant, participant.Endpoint)
		case http.StatusNotAcceptable:
			log.Error("[%s] rpc<- %s leader moved", e.participant, participant.Endpoint)
		default:
			log.Error("[%s] rpc<- %s %d", e.participant, participant.Endpoint, statusCode)
		}

	}
}

func (e *Engine) callRPC(endpoint string, epoch int, resources []cluster.Resource) int {
	resp, _, errs := gorequest.New().
		Post(fmt.Sprintf("http://%s/v1/rebalance?epoch=%d", endpoint, epoch)).
		Set("User-Agent", fmt.Sprintf("dbus-%s", dbus.Revision)).
		SendString(string(cluster.Resources(resources).Marshal())).
		End()
	if len(errs) > 0 {
		// e,g. participant gone
		// connection reset
		// connnection refused
		// FIXME what if connection timeout?
		return http.StatusGone
	}

	return resp.StatusCode
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/stretchr/testify/assert"
	"testing"
)

func Test_ToStringWithoutKeys(t *testing.T) {
	env := Environment{Name: "production"}
	assert.Equal(t, env.ToString(), "")
}

func Test_ToStringWithKeys(t *testing.T) {
	env := Environment{
		Name: "production",
		Keys: []Key{
			Key{Name: "foo", Value: "bar"},
			Key{Name: "hello", Value: "world"},
		},
	}

	assert.Equal(t, env.ToString(), "FOO=bar\nHELLO=world")
}

func Test_readEnvironments(t *testing.T) {
	envs := readEnvironments("./examples/myapp")

	assert.Equal(t, len(envs), 2)

	assert.Equal(t, envs[0].Name, "production")
	assert.Equal(t, len(envs[0].Keys), 4)
	assert.Equal(t, len(envs[0].Hosts), 3)
	assert.Equal(t, envs[0].Token, "sampletoken")

	assert.Equal(t, envs[1].Name, "staging")
	assert.Equal(t, len(envs[1].Keys), 4)
	assert.Equal(t, len(envs[1].Hosts), 0)
	assert.Equal(t, envs[1].Token, "")
}
<commit_msg>Add read hosts for the environment<commit_after>package main

import (
	"github.com/stretchr/testify/assert"
	"testing"
)

func Test_ToStringWithoutKeys(t *testing.T) {
	env := Environment{Name: "production"}
	assert.Equal(t, env.ToString(), "")
}

func Test_ToStringWithKeys(t *testing.T) {
	env := Environment{
		Name: "production",
		Keys: []Key{
			Key{Name: "foo", Value: "bar"},
			Key{Name: "hello", Value: "world"},
		},
	}

	assert.Equal(t, env.ToString(), "FOO=bar\nHELLO=world")
}

func Test_readEnvironments(t *testing.T) {
	envs := readEnvironments("./examples/myapp")

	assert.Equal(t, len(envs), 2)

	assert.Equal(t, envs[0].Name, "production")
	assert.Equal(t, len(envs[0].Keys), 4)
	assert.Equal(t, len(envs[0].Hosts), 3)
	assert.Equal(t, envs[0].Hosts, []string{"192.168.1.1", "192.168.1.2", "192.168.1.3"})
	assert.Equal(t, envs[0].Token, "sampletoken")

	assert.Equal(t, envs[1].Name, "staging")
	assert.Equal(t, len(envs[1].Keys), 4)
	assert.Equal(t, len(envs[1].Hosts), 0)
	assert.Equal(t, envs[1].Token, "")
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"flag"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"text/template"
)

type sliceFlag struct {
	TypeName     string
	Preprocess   bool
	ExampleInput string
}

// ok for this bad boy you gotta set the arguments as all the types you wanna
// generate slice flags for.
func main() {
	outputFile := flag.String("o", "-", "File to output to. Blank or - for stdin")
	testOutputFile := flag.String("ot", "", "Test file to output to. - for stdin. If unset, defaults to same base same path without extension as outputFile, with _test.go appended")
	templateFile := flag.String("t", "./gen/slice_flags/template.go.tmpl", "File to use as template")
	testTemplateFile := flag.String("tt", "./gen/slice_flags/template_test.go.tmpl", "File to use as template")
	exampleInput := flag.String("example-input", "", "Example input")
	preprocess := flag.Bool("preprocesser", false, "Whether Preprocessor is implemented on the single-version of these flags")

	flag.Parse()
	var err error

	if *testOutputFile == "" {
		name := strings.TrimSuffix(filepath.Base(*outputFile), ".go")
		*testOutputFile = filepath.Join(filepath.Dir(*outputFile), name+"_test.go")
	}

	args := flag.Args()
	if len(args) != 1 {
		fmt.Println("usage: go run slice_flags -o <output file> -t <template file> [-p] <type name>")
		fmt.Println("")
		fmt.Println("-p: implement Preprocesser by calling Preprocess on each value in the sliceflag slice.")
		fmt.Println("")
		fmt.Println("To generate multiple slice flags, call this multiple times")
		os.Exit(1)
	}

	data := sliceFlag{
		TypeName:     args[0],
		Preprocess:   *preprocess,
		ExampleInput: *exampleInput,
	}
	err = writeTemplate(*outputFile, *templateFile, data)
	if err != nil {
		fmt.Printf("couldn't write %s: %s\n", *outputFile, err)
		os.Exit(1)
	}
	writeTemplate(*testOutputFile, *testTemplateFile, data)
	if err != nil {
		fmt.Printf("couldn't write %s: %s\n", *testOutputFile, err)
		os.Exit(1)
	}
}

func writeTemplate(outputFile, templateFile string, data sliceFlag) (err error) {
	fmt.Println("writeTemplate started")
	var outputWriter io.WriteCloser = os.Stdout
	if outputFile != "" && outputFile != "-" {
		outputWriter, err = os.Create(outputFile)
		if err != nil {
			return
		}
	}
	defer outputWriter.Close()

	tmpl, err := template.ParseFiles(templateFile)
	if err != nil {
		return
	}

	inputReader, inputWriter := io.Pipe()
	betweenReader, betweenWriter := io.Pipe()
	gofmtPath, err := exec.LookPath("gofmt")
	if err != nil {
		return
	}
	importsPath, err := exec.LookPath("goimports")
	if err != nil {
		return
	}
	gofmt := exec.Cmd{
		Path:   gofmtPath,
		Args:   []string{"gofmt"},
		Stdin:  inputReader,
		Stdout: betweenWriter,
		Stderr: os.Stderr,
	}
	imports := exec.Cmd{
		Path:   importsPath,
		Args:   []string{"goimports"},
		Stdin:  betweenReader,
		Stdout: outputWriter,
		Stderr: os.Stderr,
	}
	err = gofmt.Start()
	if err != nil {
		return fmt.Errorf("Couldn't start gofmt: %s", err)
	}
	err = imports.Start()
	if err != nil {
		return fmt.Errorf("Couldn't start goimports: %s", err)
	}

	fmt.Println("executing template")
	err = tmpl.Execute(inputWriter, data)
	if err != nil {
		fmt.Println("template errored: %s", err)
	}

	fmt.Println("closing inputWriter")
	inputWriter.Close()

	fmt.Println("waiting for gofmt to finish")
	fmtErr := gofmt.Wait()
	if fmtErr != nil {
		fmt.Println("gofmt errored: %s", fmtErr)
	}
	betweenWriter.Close()

	fmt.Println("waiting for goimports to finish")
	importsErr := imports.Wait()
	if importsErr != nil {
		fmt.Println("goimports errored: %s", importsErr)
	}
	if err != nil || fmtErr != nil || importsErr != nil {
		err = errors.New("template/goimports/gofmt errored")
	}
	fmt.Println("returning from writeTemplate")
	return
}
<commit_msg>Fix Println calls in cmd/bytemark/app/flags/gen<commit_after>package main

import (
	"errors"
	"flag"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"text/template"
)

type sliceFlag struct {
	TypeName     string
	Preprocess   bool
	ExampleInput string
}

// ok for this bad boy you gotta set the arguments as all the types you wanna
// generate slice flags for.
func main() {
	outputFile := flag.String("o", "-", "File to output to. Blank or - for stdin")
	testOutputFile := flag.String("ot", "", "Test file to output to. - for stdin. If unset, defaults to same base same path without extension as outputFile, with _test.go appended")
	templateFile := flag.String("t", "./gen/slice_flags/template.go.tmpl", "File to use as template")
	testTemplateFile := flag.String("tt", "./gen/slice_flags/template_test.go.tmpl", "File to use as template")
	exampleInput := flag.String("example-input", "", "Example input")
	preprocess := flag.Bool("preprocesser", false, "Whether Preprocessor is implemented on the single-version of these flags")

	flag.Parse()
	var err error

	if *testOutputFile == "" {
		name := strings.TrimSuffix(filepath.Base(*outputFile), ".go")
		*testOutputFile = filepath.Join(filepath.Dir(*outputFile), name+"_test.go")
	}

	args := flag.Args()
	if len(args) != 1 {
		fmt.Println("usage: go run slice_flags -o <output file> -t <template file> [-p] <type name>")
		fmt.Println("")
		fmt.Println("-p: implement Preprocesser by calling Preprocess on each value in the sliceflag slice.")
		fmt.Println("")
		fmt.Println("To generate multiple slice flags, call this multiple times")
		os.Exit(1)
	}

	data := sliceFlag{
		TypeName:     args[0],
		Preprocess:   *preprocess,
		ExampleInput: *exampleInput,
	}
	err = writeTemplate(*outputFile, *templateFile, data)
	if err != nil {
		fmt.Printf("couldn't write %s: %s\n", *outputFile, err)
		os.Exit(1)
	}
	writeTemplate(*testOutputFile, *testTemplateFile, data)
	if err != nil {
		fmt.Printf("couldn't write %s: %s\n", *testOutputFile, err)
		os.Exit(1)
	}
}

func writeTemplate(outputFile, templateFile string, data sliceFlag) (err error) {
	fmt.Println("writeTemplate started")
	var outputWriter io.WriteCloser = os.Stdout
	if outputFile != "" && outputFile != "-" {
		outputWriter, err = os.Create(outputFile)
		if err != nil {
			return
		}
	}
	defer outputWriter.Close()

	tmpl, err := template.ParseFiles(templateFile)
	if err != nil {
		return
	}

	inputReader, inputWriter := io.Pipe()
	betweenReader, betweenWriter := io.Pipe()
	gofmtPath, err := exec.LookPath("gofmt")
	if err != nil {
		return
	}
	importsPath, err := exec.LookPath("goimports")
	if err != nil {
		return
	}
	gofmt := exec.Cmd{
		Path:   gofmtPath,
		Args:   []string{"gofmt"},
		Stdin:  inputReader,
		Stdout: betweenWriter,
		Stderr: os.Stderr,
	}
	imports := exec.Cmd{
		Path:   importsPath,
		Args:   []string{"goimports"},
		Stdin:  betweenReader,
		Stdout: outputWriter,
		Stderr: os.Stderr,
	}
	err = gofmt.Start()
	if err != nil {
		return fmt.Errorf("Couldn't start gofmt: %s", err)
	}
	err = imports.Start()
	if err != nil {
		return fmt.Errorf("Couldn't start goimports: %s", err)
	}

	// errors are echoed rather than returned from here on out in order to
	// ensure resources get cleaned up properly, otherwise we get a deadlock
	fmt.Println("executing template")
	err = tmpl.Execute(inputWriter, data)
	if err != nil {
		fmt.Printf("template errored: %s\n", err)
	}

	fmt.Println("closing inputWriter")
	inputWriter.Close()

	fmt.Println("waiting for gofmt to finish")
	fmtErr := gofmt.Wait()
	if fmtErr != nil {
		fmt.Printf("gofmt errored: %s\n", fmtErr)
	}
	betweenWriter.Close()

	fmt.Println("waiting for goimports to finish")
	importsErr := imports.Wait()
	if importsErr != nil {
		fmt.Printf("goimports errored: %s\n", importsErr)
	}
	if err != nil || fmtErr != nil || importsErr != nil {
		err = errors.New("template/goimports/gofmt errored")
	}
	fmt.Println("returning from writeTemplate")
	return
}
<|endoftext|>


<commit_before>package gkvlite

import (
	"encoding/binary"
	"errors"
	"fmt"
	"sync"
	"sync/atomic"
)

// A persistable item.
type Item struct {
	Transient interface{} // For any ephemeral data.
	Key, Val  []byte      // Val may be nil if not fetched into memory yet.
	Priority  int32       // Use rand.Int31() for probabilistic balancing.
}

// A persistable item and its persistence location.
type itemLoc struct {
	m sync.Mutex

	loc  *ploc // can be nil if item is dirty (not yet persisted).
	item *Item // can be nil if item is not fetched into memory yet.
}

var empty_itemLoc = &itemLoc{}

// Number of Key bytes plus number of Val bytes.
func (i *Item) NumBytes(c *Collection) int {
	return len(i.Key) + i.NumValBytes(c)
}

func (i *Item) NumValBytes(c *Collection) int {
	if c.store.callbacks.ItemValLength != nil {
		return c.store.callbacks.ItemValLength(c, i)
	}
	return len(i.Val)
}

// The returned Item will not have been allocated through the optional
// StoreCallbacks.ItemAlloc() callback.
func (i *Item) Copy() *Item {
	return &Item{
		Key:       i.Key,
		Val:       i.Val,
		Priority:  i.Priority,
		Transient: i.Transient,
	}
}

func (i *itemLoc) Loc() *ploc {
	i.m.Lock()
	defer i.m.Unlock()
	return i.loc
}

func (i *itemLoc) setLoc(n *ploc) {
	i.m.Lock()
	defer i.m.Unlock()
	i.loc = n
}

func (i *itemLoc) Item() *Item {
	i.m.Lock()
	defer i.m.Unlock()
	return i.item
}

func (i *itemLoc) casItem(o, n *Item) bool {
	i.m.Lock()
	defer i.m.Unlock()
	if i.item == o {
		i.item = n
		return true
	}
	return false
}

func (i *itemLoc) Copy(src *itemLoc) {
	if src == nil {
		i.Copy(empty_itemLoc)
		return
	}
	newloc := src.Loc()
	newitem := src.Item()

	i.m.Lock()
	defer i.m.Unlock()
	i.loc = newloc
	i.item = newitem
}

const itemLoc_hdrLength int = 4 + 4 + 4 + 4

func (i *itemLoc) write(c *Collection) (err error) {
	if i.Loc().isEmpty() {
		iItem := i.Item()
		if iItem == nil {
			return errors.New("itemLoc.write with nil item")
		}
		if c.store.callbacks.BeforeItemWrite != nil {
			iItem, err = c.store.callbacks.BeforeItemWrite(c, iItem)
			if err != nil {
				return err
			}
		}
		offset := atomic.LoadInt64(&c.store.size)
		hlength := itemLoc_hdrLength + len(iItem.Key)
		vlength := iItem.NumValBytes(c)
		ilength := hlength + vlength
		b := make([]byte, hlength)
		pos := 0
		binary.BigEndian.PutUint32(b[pos:pos+4], uint32(ilength))
		pos += 4
		binary.BigEndian.PutUint32(b[pos:pos+4], uint32(len(iItem.Key)))
		pos += 4
		binary.BigEndian.PutUint32(b[pos:pos+4], uint32(vlength))
		pos += 4
		binary.BigEndian.PutUint32(b[pos:pos+4], uint32(iItem.Priority))
		pos += 4
		pos += copy(b[pos:], iItem.Key)
		if pos != hlength {
			return fmt.Errorf("itemLoc.write() pos: %v didn't match hlength: %v",
				pos, hlength)
		}
		if _, err := c.store.file.WriteAt(b, offset); err != nil {
			return err
		}
		err := c.store.ItemValWrite(c, iItem, c.store.file, offset+int64(pos))
		if err != nil {
			return err
		}
		atomic.StoreInt64(&c.store.size, offset+int64(ilength))
		i.setLoc(&ploc{Offset: offset, Length: uint32(ilength)})
	}
	return nil
}

func (iloc *itemLoc) read(c *Collection, withValue bool) (icur *Item, err error) {
	if iloc == nil {
		return nil, nil
	}
	icur = iloc.Item()
	if icur == nil || (icur.Val == nil && withValue) {
		loc := iloc.Loc()
		if loc.isEmpty() {
			return nil, nil
		}
		if loc.Length < uint32(itemLoc_hdrLength) {
			return nil, fmt.Errorf("unexpected item loc.Length: %v < %v",
				loc.Length, itemLoc_hdrLength)
		}
		b := make([]byte, itemLoc_hdrLength)
		if _, err := c.store.file.ReadAt(b, loc.Offset); err != nil {
			return nil, err
		}
		pos := 0
		length := binary.BigEndian.Uint32(b[pos : pos+4])
		pos += 4
		keyLength := binary.BigEndian.Uint32(b[pos : pos+4])
		pos += 4
		valLength := binary.BigEndian.Uint32(b[pos : pos+4])
		pos += 4
		i := c.store.ItemAlloc(c, keyLength)
		if i == nil {
			return nil, errors.New("ItemAlloc() failed")
		}
		i.Priority = int32(binary.BigEndian.Uint32(b[pos : pos+4]))
		pos += 4
		if length != uint32(itemLoc_hdrLength)+uint32(keyLength)+valLength {
			c.store.ItemDecRef(c, i)
			return nil, errors.New("mismatched itemLoc lengths")
		}
		if pos != itemLoc_hdrLength {
			c.store.ItemDecRef(c, i)
			return nil, fmt.Errorf("read pos != itemLoc_hdrLength, %v != %v",
				pos, itemLoc_hdrLength)
		}
		if _, err := c.store.file.ReadAt(i.Key,
			loc.Offset+int64(itemLoc_hdrLength)); err != nil {
			c.store.ItemDecRef(c, i)
			return nil, err
		}
		if withValue {
			err := c.store.ItemValRead(c, i, c.store.file,
				loc.Offset+int64(itemLoc_hdrLength)+int64(keyLength), valLength)
			if err != nil {
				c.store.ItemDecRef(c, i)
				return nil, err
			}
		}
		if c.store.callbacks.AfterItemRead != nil {
			i, err = c.store.callbacks.AfterItemRead(c, i)
			if err != nil {
				c.store.ItemDecRef(c, i)
				return nil, err
			}
		}
		if !iloc.casItem(icur, i) {
			c.store.ItemDecRef(c, i)
			return iloc.read(c, withValue)
		}
		if icur != nil {
			c.store.ItemDecRef(c, icur)
		}
		icur = i
	}
	return icur, nil
}

func (iloc *itemLoc) NumBytes(c *Collection) int {
	loc := iloc.Loc()
	if loc.isEmpty() {
		i := iloc.Item()
		if i == nil {
			return 0
		}
		return i.NumBytes(c)
	}
	return int(loc.Length) - itemLoc_hdrLength
}
<commit_msg>Fix race in item too.<commit_after>package gkvlite

import (
	"encoding/binary"
	"errors"
	"fmt"
	"sync"
	"sync/atomic"
)

// A persistable item.
type Item struct {
	Transient interface{} // For any ephemeral data.
	Key, Val  []byte      // Val may be nil if not fetched into memory yet.
	Priority  int32       // Use rand.Int31() for probabilistic balancing.
}

var itemLocGL = sync.RWMutex{}

// A persistable item and its persistence location.
type itemLoc struct {
	loc  *ploc // can be nil if item is dirty (not yet persisted).
	item *Item // can be nil if item is not fetched into memory yet.
}

var empty_itemLoc = &itemLoc{}

// Number of Key bytes plus number of Val bytes.
func (i *Item) NumBytes(c *Collection) int {
	return len(i.Key) + i.NumValBytes(c)
}

func (i *Item) NumValBytes(c *Collection) int {
	if c.store.callbacks.ItemValLength != nil {
		return c.store.callbacks.ItemValLength(c, i)
	}
	return len(i.Val)
}

// The returned Item will not have been allocated through the optional
// StoreCallbacks.ItemAlloc() callback.
func (i *Item) Copy() *Item {
	return &Item{
		Key:       i.Key,
		Val:       i.Val,
		Priority:  i.Priority,
		Transient: i.Transient,
	}
}

func (i *itemLoc) Loc() *ploc {
	itemLocGL.RLock()
	defer itemLocGL.RUnlock()
	return i.loc
}

func (i *itemLoc) setLoc(n *ploc) {
	itemLocGL.Lock()
	defer itemLocGL.Unlock()
	i.loc = n
}

func (i *itemLoc) Item() *Item {
	itemLocGL.RLock()
	defer itemLocGL.RUnlock()
	return i.item
}

func (i *itemLoc) casItem(o, n *Item) bool {
	itemLocGL.Lock()
	defer itemLocGL.Unlock()
	if i.item == o {
		i.item = n
		return true
	}
	return false
}

func (i *itemLoc) Copy(src *itemLoc) {
	if src == nil {
		i.Copy(empty_itemLoc)
		return
	}

	itemLocGL.Lock()
	defer itemLocGL.Unlock()
	// NOTE: This trick only works because of the global lock. No reason to lock
	// src independently of i.
	i.loc = src.loc
	i.item = src.item
}

const itemLoc_hdrLength int = 4 + 4 + 4 + 4

func (i *itemLoc) write(c *Collection) (err error) {
	if i.Loc().isEmpty() {
		iItem := i.Item()
		if iItem == nil {
			return errors.New("itemLoc.write with nil item")
		}
		if c.store.callbacks.BeforeItemWrite != nil {
			iItem, err = c.store.callbacks.BeforeItemWrite(c, iItem)
			if err != nil {
				return err
			}
		}
		offset := atomic.LoadInt64(&c.store.size)
		hlength := itemLoc_hdrLength + len(iItem.Key)
		vlength := iItem.NumValBytes(c)
		ilength := hlength + vlength
		b := make([]byte, hlength)
		pos := 0
		binary.BigEndian.PutUint32(b[pos:pos+4], uint32(ilength))
		pos += 4
		binary.BigEndian.PutUint32(b[pos:pos+4], uint32(len(iItem.Key)))
		pos += 4
		binary.BigEndian.PutUint32(b[pos:pos+4], uint32(vlength))
		pos += 4
		binary.BigEndian.PutUint32(b[pos:pos+4], uint32(iItem.Priority))
		pos += 4
		pos += copy(b[pos:], iItem.Key)
		if pos != hlength {
			return fmt.Errorf("itemLoc.write() pos: %v didn't match hlength: %v",
				pos, hlength)
		}
		if _, err := c.store.file.WriteAt(b, offset); err != nil {
			return err
		}
		err := c.store.ItemValWrite(c, iItem, c.store.file, offset+int64(pos))
		if err != nil {
			return err
		}
		atomic.StoreInt64(&c.store.size, offset+int64(ilength))
		i.setLoc(&ploc{Offset: offset, Length: uint32(ilength)})
	}
	return nil
}

func (iloc *itemLoc) read(c *Collection, withValue bool) (icur *Item, err error) {
	if iloc == nil {
		return nil, nil
	}
	icur = iloc.Item()
	if icur == nil || (icur.Val == nil && withValue) {
		loc := iloc.Loc()
		if loc.isEmpty() {
			return nil, nil
		}
		if loc.Length < uint32(itemLoc_hdrLength) {
			return nil, fmt.Errorf("unexpected item loc.Length: %v < %v",
				loc.Length, itemLoc_hdrLength)
		}
		b := make([]byte, itemLoc_hdrLength)
		if _, err := c.store.file.ReadAt(b, loc.Offset); err != nil {
			return nil, err
		}
		pos := 0
		length := binary.BigEndian.Uint32(b[pos : pos+4])
		pos += 4
		keyLength := binary.BigEndian.Uint32(b[pos : pos+4])
		pos += 4
		valLength := binary.BigEndian.Uint32(b[pos : pos+4])
		pos += 4
		i := c.store.ItemAlloc(c, keyLength)
		if i == nil {
			return nil, errors.New("ItemAlloc() failed")
		}
		i.Priority = int32(binary.BigEndian.Uint32(b[pos : pos+4]))
		pos += 4
		if length != uint32(itemLoc_hdrLength)+uint32(keyLength)+valLength {
			c.store.ItemDecRef(c, i)
			return nil, errors.New("mismatched itemLoc lengths")
		}
		if pos != itemLoc_hdrLength {
			c.store.ItemDecRef(c, i)
			return nil, fmt.Errorf("read pos != itemLoc_hdrLength, %v != %v",
				pos, itemLoc_hdrLength)
		}
		if _, err := c.store.file.ReadAt(i.Key,
			loc.Offset+int64(itemLoc_hdrLength)); err != nil {
			c.store.ItemDecRef(c, i)
			return nil, err
		}
		if withValue {
			err := c.store.ItemValRead(c, i, c.store.file,
				loc.Offset+int64(itemLoc_hdrLength)+int64(keyLength), valLength)
			if err != nil {
				c.store.ItemDecRef(c, i)
				return nil, err
			}
		}
		if c.store.callbacks.AfterItemRead != nil {
			i, err = c.store.callbacks.AfterItemRead(c, i)
			if err != nil {
				c.store.ItemDecRef(c, i)
				return nil, err
			}
		}
		if !iloc.casItem(icur, i) {
			c.store.ItemDecRef(c, i)
			return iloc.read(c, withValue)
		}
		if icur != nil {
			c.store.ItemDecRef(c, icur)
		}
		icur = i
	}
	return icur, nil
}

func (iloc *itemLoc) NumBytes(c *Collection) int {
	loc := iloc.Loc()
	if loc.isEmpty() {
		i := iloc.Item()
		if i == nil {
			return 0
		}
		return i.NumBytes(c)
	}
	return int(loc.Length) - itemLoc_hdrLength
}
<|endoftext|>


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

package consumergroup

import (
	"context"
	"fmt"

	"go.uber.org/zap"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	eventingduckv1alpha1 "knative.dev/eventing/pkg/apis/duck/v1alpha1"
	"knative.dev/eventing/pkg/scheduler"
	kubeclient "knative.dev/pkg/client/injection/kube/client"
	"knative.dev/pkg/logging"

	internalv1alpha1 "knative.dev/eventing-kafka-broker/control-plane/pkg/client/internals/kafka/clientset/versioned/typed/eventing/v1alpha1"
	kafkainternalsclient "knative.dev/eventing-kafka-broker/control-plane/pkg/client/internals/kafka/injection/client"
)

type evictor struct {
	ctx context.Context

	kubeClient      kubernetes.Interface
	InternalsClient internalv1alpha1.InternalV1alpha1Interface

	logger *zap.Logger
}

// newEvictor creates a new evictor.
//
// fields are additional logger fields to be attached to the evictor logger.
func newEvictor(ctx context.Context, fields ...zap.Field) evictor {
	return evictor{
		ctx:             ctx,
		kubeClient:      kubeclient.Get(ctx),
		InternalsClient: kafkainternalsclient.Get(ctx).InternalV1alpha1(),
		logger: logging.FromContext(ctx).
			Desugar().
			With(zap.String("component", "evictor")).
			With(fields...),
	}
}

func (e *evictor) evict(pod *corev1.Pod, vpod scheduler.VPod, from *eventingduckv1alpha1.Placement) error {
	key := vpod.GetKey()

	logger := e.logger.
		With(zap.String("consumergroup", key.String())).
		With(zap.String("pod", fmt.Sprintf("%s/%s", pod.GetNamespace(), pod.GetName())))

	if err := e.disablePodScheduling(logger, pod.DeepCopy() /* Do not modify informer copy. */); err != nil {
		return fmt.Errorf("failed to mark pod unschedulable: %w", err)
	}

	cg, err := e.InternalsClient.
		ConsumerGroups(key.Namespace).
		Get(e.ctx, key.Name, metav1.GetOptions{})
	if apierrors.IsNotFound(err) {
		return nil
	}
	if err != nil {
		return fmt.Errorf("failed to get consumer group %s/%s: %w", key.Namespace, key.Name, err)
	}

	// Do not evict when the consumer group isn't scheduled yet.
	if cg.IsNotScheduled() {
		return nil
	}

	cg.Status.Placements = removePlacement(cg.GetPlacements(), from)

	_, err = e.InternalsClient.
		ConsumerGroups(cg.GetNamespace()).
		Update(e.ctx, cg, metav1.UpdateOptions{})
	if apierrors.IsNotFound(err) {
		return nil
	}
	if err != nil {
		return fmt.Errorf("failed to update consumer group %s/%s: %w", cg.GetNamespace(), cg.GetName(), err)
	}

	return nil
}

func (e *evictor) disablePodScheduling(logger *zap.Logger, pod *corev1.Pod) error {
	if pod.Annotations == nil {
		pod.Annotations = make(map[string]string, 1)
	}
	// scheduling disabled.
	pod.Annotations[scheduler.PodAnnotationKey] = "true"

	_, err := e.kubeClient.CoreV1().
		Pods(pod.GetNamespace()).
		Update(e.ctx, pod, metav1.UpdateOptions{})
	if err != nil {
		return fmt.Errorf("failed to update pod %s/%s: %w", pod.GetNamespace(), pod.GetName(), err)
	}

	logger.Info("Marked pod as unschedulable")

	return nil
}

func removePlacement(before []eventingduckv1alpha1.Placement, toRemove *eventingduckv1alpha1.Placement) []eventingduckv1alpha1.Placement {
	after := make([]eventingduckv1alpha1.Placement, 0, len(before))
	for _, p := range before {
		if p.PodName != toRemove.PodName {
			after = append(after, p)
		}
	}
	return after
}
<commit_msg>Remove unnecessary pointer to evictor.evict method (#1853)<commit_after>/*
 * Copyright 2022 The Knative Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package consumergroup

import (
	"context"
	"fmt"

	"go.uber.org/zap"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	eventingduckv1alpha1 "knative.dev/eventing/pkg/apis/duck/v1alpha1"
	"knative.dev/eventing/pkg/scheduler"
	kubeclient "knative.dev/pkg/client/injection/kube/client"
	"knative.dev/pkg/logging"

	internalv1alpha1 "knative.dev/eventing-kafka-broker/control-plane/pkg/client/internals/kafka/clientset/versioned/typed/eventing/v1alpha1"
	kafkainternalsclient "knative.dev/eventing-kafka-broker/control-plane/pkg/client/internals/kafka/injection/client"
)

type evictor struct {
	ctx context.Context

	kubeClient      kubernetes.Interface
	InternalsClient internalv1alpha1.InternalV1alpha1Interface

	logger *zap.Logger
}

// newEvictor creates a new evictor.
//
// fields are additional logger fields to be attached to the evictor logger.
func newEvictor(ctx context.Context, fields ...zap.Field) evictor {
	return evictor{
		ctx:             ctx,
		kubeClient:      kubeclient.Get(ctx),
		InternalsClient: kafkainternalsclient.Get(ctx).InternalV1alpha1(),
		logger: logging.FromContext(ctx).
			Desugar().
			With(zap.String("component", "evictor")).
			With(fields...),
	}
}

func (e evictor) evict(pod *corev1.Pod, vpod scheduler.VPod, from *eventingduckv1alpha1.Placement) error {
	key := vpod.GetKey()

	logger := e.logger.
		With(zap.String("consumergroup", key.String())).
		With(zap.String("pod", fmt.Sprintf("%s/%s", pod.GetNamespace(), pod.GetName())))

	if err := e.disablePodScheduling(logger, pod.DeepCopy() /* Do not modify informer copy. */); err != nil {
		return fmt.Errorf("failed to mark pod unschedulable: %w", err)
	}

	cg, err := e.InternalsClient.
		ConsumerGroups(key.Namespace).
		Get(e.ctx, key.Name, metav1.GetOptions{})
	if apierrors.IsNotFound(err) {
		return nil
	}
	if err != nil {
		return fmt.Errorf("failed to get consumer group %s/%s: %w", key.Namespace, key.Name, err)
	}

	// Do not evict when the consumer group isn't scheduled yet.
	if cg.IsNotScheduled() {
		return nil
	}

	cg.Status.Placements = removePlacement(cg.GetPlacements(), from)

	_, err = e.InternalsClient.
		ConsumerGroups(cg.GetNamespace()).
		Update(e.ctx, cg, metav1.UpdateOptions{})
	if apierrors.IsNotFound(err) {
		return nil
	}
	if err != nil {
		return fmt.Errorf("failed to update consumer group %s/%s: %w", cg.GetNamespace(), cg.GetName(), err)
	}

	return nil
}

func (e *evictor) disablePodScheduling(logger *zap.Logger, pod *corev1.Pod) error {
	if pod.Annotations == nil {
		pod.Annotations = make(map[string]string, 1)
	}
	// scheduling disabled.
	pod.Annotations[scheduler.PodAnnotationKey] = "true"

	_, err := e.kubeClient.CoreV1().
		Pods(pod.GetNamespace()).
		Update(e.ctx, pod, metav1.UpdateOptions{})
	if err != nil {
		return fmt.Errorf("failed to update pod %s/%s: %w", pod.GetNamespace(), pod.GetName(), err)
	}

	logger.Info("Marked pod as unschedulable")

	return nil
}

func removePlacement(before []eventingduckv1alpha1.Placement, toRemove *eventingduckv1alpha1.Placement) []eventingduckv1alpha1.Placement {
	after := make([]eventingduckv1alpha1.Placement, 0, len(before))
	for _, p := range before {
		if p.PodName != toRemove.PodName {
			after = append(after, p)
		}
	}
	return after
}
<|endoftext|>


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

package i18n

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/Unknwon/macaron"
	. "github.com/smartystreets/goconvey/convey"
)

func Test_Version(t *testing.T) {
	Convey("Check package version", t, func() {
		So(Version(), ShouldEqual, _VERSION)
	})
}

func Test_I18n(t *testing.T) {
	Convey("Use i18n middleware", t, func() {
		Convey("No langauge", func() {
			defer func() {
				So(recover(), ShouldNotBeNil)
			}()

			m := macaron.New()
			m.Use(I18n(Options{}))
		})

		Convey("Languages and names not match", func() {
			defer func() {
				So(recover(), ShouldNotBeNil)
			}()

			m := macaron.New()
			m.Use(I18n(Options{
				Langs: []string{"en-US"},
			}))
		})

		Convey("Invalid directory", func() {
			defer func() {
				So(recover(), ShouldNotBeNil)
			}()

			m := macaron.New()
			m.Use(I18n(Options{
				Directory: "404",
				Langs:     []string{"en-US"},
				Names:     []string{"English"},
			}))
		})

		Convey("With correct options", func() {
			m := macaron.New()
			m.Use(I18n(Options{
				Langs: []string{"en-US"},
				Names: []string{"English"},
			}))
			m.Get("/", func() {})

			resp := httptest.NewRecorder()
			req, err := http.NewRequest("GET", "/", nil)
			So(err, ShouldBeNil)
			m.ServeHTTP(resp, req)
		})

		Convey("Set by redirect of URL parameter", func() {
			m := macaron.New()
			m.Use(I18n(Options{
				Langs:    []string{"en-US"},
				Names:    []string{"English"},
				Redirect: true,
			}))
			m.Get("/", func() {})

			resp := httptest.NewRecorder()
			req, err := http.NewRequest("GET", "/?lang=en-US", nil)
			So(err, ShouldBeNil)
			req.RequestURI = "/?lang=en-US"
			m.ServeHTTP(resp, req)
		})

		Convey("Set by Accept-Language", func() {
			m := macaron.New()
			m.Use(I18n(Options{
				Langs: []string{"en-US", "zh-CN"},
				Names: []string{"English", "简体中文"},
			}))
			m.Get("/", func(l Locale) {
				So(l.Language(), ShouldEqual, "en-US")
			})

			resp := httptest.NewRecorder()
			req, err := http.NewRequest("GET", "/", nil)
			So(err, ShouldBeNil)
			req.Header.Set("Accept-Language", "en-US")
			m.ServeHTTP(resp, req)
		})
	})
}
<commit_msg>add test<commit_after>// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package i18n

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/Unknwon/macaron"
	. "github.com/smartystreets/goconvey/convey"
)

func Test_Version(t *testing.T) {
	Convey("Check package version", t, func() {
		So(Version(), ShouldEqual, _VERSION)
	})
}

func Test_I18n(t *testing.T) {
	Convey("Use i18n middleware", t, func() {
		Convey("No langauge", func() {
			defer func() {
				So(recover(), ShouldNotBeNil)
			}()

			m := macaron.New()
			m.Use(I18n(Options{}))
		})

		Convey("Languages and names not match", func() {
			defer func() {
				So(recover(), ShouldNotBeNil)
			}()

			m := macaron.New()
			m.Use(I18n(Options{
				Langs: []string{"en-US"},
			}))
		})

		Convey("Invalid directory", func() {
			defer func() {
				So(recover(), ShouldNotBeNil)
			}()

			m := macaron.New()
			m.Use(I18n(Options{
				Directory: "404",
				Langs:     []string{"en-US"},
				Names:     []string{"English"},
			}))
		})

		Convey("With correct options", func() {
			m := macaron.New()
			m.Use(I18n(Options{
				Files: map[string][]byte{"locale_en-US.ini": []byte("")},
				Langs: []string{"en-US"},
				Names: []string{"English"},
			}))
			m.Get("/", func() {})

			resp := httptest.NewRecorder()
			req, err := http.NewRequest("GET", "/", nil)
			So(err, ShouldBeNil)
			m.ServeHTTP(resp, req)
		})

		Convey("Set by redirect of URL parameter", func() {
			m := macaron.New()
			m.Use(I18n(Options{
				Langs:    []string{"en-US"},
				Names:    []string{"English"},
				Redirect: true,
			}))
			m.Get("/", func() {})

			resp := httptest.NewRecorder()
			req, err := http.NewRequest("GET", "/?lang=en-US", nil)
			So(err, ShouldBeNil)
			req.RequestURI = "/?lang=en-US"
			m.ServeHTTP(resp, req)
		})

		Convey("Set by Accept-Language", func() {
			m := macaron.New()
			m.Use(I18n(Options{
				Langs: []string{"en-US", "zh-CN"},
				Names: []string{"English", "简体中文"},
			}))
			m.Get("/", func(l Locale) {
				So(l.Language(), ShouldEqual, "en-US")
			})

			resp := httptest.NewRecorder()
			req, err := http.NewRequest("GET", "/", nil)
			So(err, ShouldBeNil)
			req.Header.Set("Accept-Language", "en-US")
			m.ServeHTTP(resp, req)
		})
	})
}
<|endoftext|>


<commit_before>package revel

import (
	"io/ioutil"
	"log"
	"net/http"
	"testing"
	"time"

	"github.com/revel/revel/config"
)

const (
	testDataPath   string = "testdata/i18n"
	testConfigPath string = "testdata/i18n/config"
	testConfigName string = "test_app.conf"
)

func TestI18nLoadMessages(t *testing.T) {
	loadMessages(testDataPath)

	// Assert that we have the expected number of languages
	if len(MessageLanguages()) != 2 {
		t.Fatalf("Expected messages to contain no more or less than 2 languages, instead there are %d languages", len(MessageLanguages()))
	}
}

func TestI18nMessage(t *testing.T) {
	loadMessages(testDataPath)
	loadTestI18nConfig(t)

	// Assert that we can get a message and we get the expected return value
	if message := Message("nl", "greeting"); message != "Hallo" {
		t.Errorf("Message 'greeting' for locale 'nl' (%s) does not have the expected value", message)
	}
	if message := Message("en", "greeting"); message != "Hello" {
		t.Errorf("Message 'greeting' for locale 'en' (%s) does not have the expected value", message)
	}
	if message := Message("en", "folded"); message != "Greeting is 'Hello'" {
		t.Error("Unexpected unfolded message: ", message)
	}
	if message := Message("en", "arguments.string", "Vincent Hanna"); message != "My name is Vincent Hanna" {
		t.Errorf("Message 'arguments.string' for locale 'en' (%s) does not have the expected value", message)
	}
	if message := Message("en", "arguments.hex", 1234567, 1234567); message != "The number 1234567 in hexadecimal notation would be 12d687" {
		t.Errorf("Message 'arguments.hex' for locale 'en' (%s) does not have the expected value", message)
	}
	if message := Message("en", "folded.arguments", 12345); message != "Rob is 12345 years old" {
		t.Errorf("Message 'folded.arguments' for locale 'en' (%s) does not have the expected value", message)
	}
	if message := Message("en-AU", "greeting"); message != "G'day" {
		t.Errorf("Message 'greeting' for locale 'en-AU' (%s) does not have the expected value", message)
	}
	if message := Message("en-AU", "only_exists_in_default"); message != "Default" {
		t.Errorf("Message 'only_exists_in_default' for locale 'en-AU' (%s) does not have the expected value", message)
	}

	// Assert that message merging works
	if message := Message("en", "greeting2"); message != "Yo!" {
		t.Errorf("Message 'greeting2' for locale 'en' (%s) does not have the expected value", message)
	}

	// Assert that we get the expected return value for a locale that doesn't exist
	if message := Message("unknown locale", "message"); message != "??? message ???" {
		t.Error("Locale 'unknown locale' is not supposed to exist")
	}
	// Assert that we get the expected return value for a message that doesn't exist
	if message := Message("nl", "unknown message"); message != "??? unknown message ???" {
		t.Error("Message 'unknown message' is not supposed to exist")
	}
}

func TestI18nMessageWithDefaultLocale(t *testing.T) {
	loadMessages(testDataPath)
	loadTestI18nConfig(t)

	if message := Message("doesn't exist", "greeting"); message != "Hello" {
		t.Errorf("Expected message '%s' for unknown locale to be default '%s' but was '%s'", "greeting", "Hello", message)
	}
	if message := Message("doesn't exist", "unknown message"); message != "??? unknown message ???" {
		t.Error("Message 'unknown message' is not supposed to exist in the default language")
	}
}

func TestHasLocaleCookie(t *testing.T) {
	loadTestI18nConfig(t)

	if found, value := hasLocaleCookie(buildRequestWithCookie("APP_LANG", "en")); !found {
		t.Errorf("Expected %s cookie with value '%s' but found nothing or unexpected value '%s'", "APP_LANG", "en", value)
	}
	if found, value := hasLocaleCookie(buildRequestWithCookie("APP_LANG", "en-US")); !found {
		t.Errorf("Expected %s cookie with value '%s' but found nothing or unexpected value '%s'", "APP_LANG", "en-US", value)
	}
	if found, _ := hasLocaleCookie(buildRequestWithCookie("DOESNT_EXIST", "en-US")); found {
		t.Errorf("Expected %s cookie to not exist, but apparently it does", "DOESNT_EXIST")
	}
}

func TestHasLocaleCookieWithInvalidConfig(t *testing.T) {
	loadTestI18nConfigWithoutLanguageCookieOption(t)
	if found, _ := hasLocaleCookie(buildRequestWithCookie("APP_LANG", "en-US")); found {
		t.Errorf("Expected %s cookie to not exist because the configured name is missing", "APP_LANG")
	}
	if found, _ := hasLocaleCookie(buildRequestWithCookie("REVEL_LANG", "en-US")); !found {
		t.Errorf("Expected %s cookie to exist", "REVEL_LANG")
	}
}

func TestHasAcceptLanguageHeader(t *testing.T) {
	if found, value := hasAcceptLanguageHeader(buildRequestWithAcceptLanguages("en-US")); !found && value != "en-US" {
		t.Errorf("Expected to find Accept-Language header with value '%s', found '%s' instead", "en-US", value)
	}
	if found, value := hasAcceptLanguageHeader(buildRequestWithAcceptLanguages("en-GB", "en-US", "nl")); !found && value != "en-GB" {
		t.Errorf("Expected to find Accept-Language header with value '%s', found '%s' instead", "en-GB", value)
	}
}

func TestBeforeRequest(t *testing.T) {
	loadTestI18nConfig(t)

	c := NewController(buildEmptyRequest(), nil)
	if I18nFilter(c, NilChain); c.Request.Locale != "" {
		t.Errorf("Expected to find current language '%s' in controller, found '%s' instead", "", c.Request.Locale)
	}

	c = NewController(buildRequestWithCookie("APP_LANG", "en-US"), nil)
	if I18nFilter(c, NilChain); c.Request.Locale != "en-US" {
		t.Errorf("Expected to find current language '%s' in controller, found '%s' instead", "en-US", c.Request.Locale)
	}

	c = NewController(buildRequestWithAcceptLanguages("en-GB", "en-US"), nil)
	if I18nFilter(c, NilChain); c.Request.Locale != "en-GB" {
		t.Errorf("Expected to find current language '%s' in controller, found '%s' instead", "en-GB", c.Request.Locale)
	}
}

func TestI18nMessageUnknownValueFormat(t *testing.T) {
	loadMessages(testDataPath)
	loadTestI18nConfigWithUnknowFormatOption(t)

	// Assert that we can get a message and we get the expected return value
	if message := Message("en", "greeting"); message != "Hello" {
		t.Errorf("Message 'greeting' for locale 'en' (%s) does not have the expected value", message)
	}

	// Assert that we get the expected return value with original format
	if message := Message("unknown locale", "message"); message != "*** message ***" {
		t.Error("Locale 'unknown locale' is not supposed to exist")
	}
	if message := Message("nl", "unknown message"); message != "*** unknown message ***" {
		t.Error("Message 'unknown message' is not supposed to exist")
	}
}

func BenchmarkI18nLoadMessages(b *testing.B) {
	excludeFromTimer(b, func() { TRACE = log.New(ioutil.Discard, "", 0) })

	for i := 0; i < b.N; i++ {
		loadMessages(testDataPath)
	}
}

func BenchmarkI18nMessage(b *testing.B) {
	for i := 0; i < b.N; i++ {
		Message("nl", "greeting")
	}
}

func BenchmarkI18nMessageWithArguments(b *testing.B) {
	excludeFromTimer(b, func() { TRACE = log.New(ioutil.Discard, "", 0) })

	for i := 0; i < b.N; i++ {
		Message("en", "arguments.string", "Vincent Hanna")
	}
}

func BenchmarkI18nMessageWithFoldingAndArguments(b *testing.B) {
	excludeFromTimer(b, func() { TRACE = log.New(ioutil.Discard, "", 0) })

	for i := 0; i < b.N; i++ {
		Message("en", "folded.arguments", 12345)
	}
}

// Exclude whatever operations the given function performs from the benchmark timer.
func excludeFromTimer(b *testing.B, f func()) {
	b.StopTimer()
	f()
	b.StartTimer()
}

func loadTestI18nConfig(t *testing.T) {
	ConfPaths = append(ConfPaths, testConfigPath)
	testConfig, err := config.LoadContext(testConfigName, ConfPaths)
	if err != nil {
		t.Fatalf("Unable to load test config '%s': %s", testConfigName, err.Error())
	}
	Config = testConfig
	CookiePrefix = Config.StringDefault("cookie.prefix", "REVEL")
}

func loadTestI18nConfigWithoutLanguageCookieOption(t *testing.T) {
	loadTestI18nConfig(t)
	Config.Raw().RemoveOption("DEFAULT", "i18n.cookie")
}

func loadTestI18nConfigWithUnknowFormatOption(t *testing.T) {
	loadTestI18nConfig(t)
	Config.config.AddOption("DEFAULT", "i18n.unknown_format", "*** %s ***")
}

func buildRequestWithCookie(name, value string) *Request {
	httpRequest, _ := http.NewRequest("GET", "/", nil)
	request := NewRequest(httpRequest)
	request.AddCookie(&http.Cookie{
		Name:       name,
		Value:      value,
		Path:       "",
		Domain:     "",
		Expires:    time.Now(),
		RawExpires: "",
		MaxAge:     0,
		Secure:     false,
		HttpOnly:   false,
		Raw:        "",
		Unparsed:   nil,
	})
	return request
}

func buildRequestWithAcceptLanguages(acceptLanguages ...string) *Request {
	httpRequest, _ := http.NewRequest("GET", "/", nil)
	request := NewRequest(httpRequest)
	for _, acceptLanguage := range acceptLanguages {
		request.AcceptLanguages = append(request.AcceptLanguages, AcceptLanguage{acceptLanguage, 1})
	}
	return request
}

func buildEmptyRequest() *Request {
	httpRequest, _ := http.NewRequest("GET", "/", nil)
	request := NewRequest(httpRequest)
	return request
}
<commit_msg>Updated test for new config package<commit_after>package revel

import (
	"io/ioutil"
	"log"
	"net/http"
	"testing"
	"time"

	"github.com/revel/revel/config"
)

const (
	testDataPath   string = "testdata/i18n"
	testConfigPath string = "testdata/i18n/config"
	testConfigName string = "test_app.conf"
)

func TestI18nLoadMessages(t *testing.T) {
	loadMessages(testDataPath)

	// Assert that we have the expected number of languages
	if len(MessageLanguages()) != 2 {
		t.Fatalf("Expected messages to contain no more or less than 2 languages, instead there are %d languages", len(MessageLanguages()))
	}
}

func TestI18nMessage(t *testing.T) {
	loadMessages(testDataPath)
	loadTestI18nConfig(t)

	// Assert that we can get a message and we get the expected return value
	if message := Message("nl", "greeting"); message != "Hallo" {
		t.Errorf("Message 'greeting' for locale 'nl' (%s) does not have the expected value", message)
	}
	if message := Message("en", "greeting"); message != "Hello" {
		t.Errorf("Message 'greeting' for locale 'en' (%s) does not have the expected value", message)
	}
	if message := Message("en", "folded"); message != "Greeting is 'Hello'" {
		t.Error("Unexpected unfolded message: ", message)
	}
	if message := Message("en", "arguments.string", "Vincent Hanna"); message != "My name is Vincent Hanna" {
		t.Errorf("Message 'arguments.string' for locale 'en' (%s) does not have the expected value", message)
	}
	if message := Message("en", "arguments.hex", 1234567, 1234567); message != "The number 1234567 in hexadecimal notation would be 12d687" {
		t.Errorf("Message 'arguments.hex' for locale 'en' (%s) does not have the expected value", message)
	}
	if message := Message("en", "folded.arguments", 12345); message != "Rob is 12345 years old" {
		t.Errorf("Message 'folded.arguments' for locale 'en' (%s) does not have the expected value", message)
	}
	if message := Message("en-AU", "greeting"); message != "G'day" {
		t.Errorf("Message 'greeting' for locale 'en-AU' (%s) does not have the expected value", message)
	}
	if message := Message("en-AU", "only_exists_in_default"); message != "Default" {
		t.Errorf("Message 'only_exists_in_default' for locale 'en-AU' (%s) does not have the expected value", message)
	}

	// Assert that message merging works
	if message := Message("en", "greeting2"); message != "Yo!" {
		t.Errorf("Message 'greeting2' for locale 'en' (%s) does not have the expected value", message)
	}

	// Assert that we get the expected return value for a locale that doesn't exist
	if message := Message("unknown locale", "message"); message != "??? message ???" {
		t.Error("Locale 'unknown locale' is not supposed to exist")
	}
	// Assert that we get the expected return value for a message that doesn't exist
	if message := Message("nl", "unknown message"); message != "??? unknown message ???" {
		t.Error("Message 'unknown message' is not supposed to exist")
	}
}

func TestI18nMessageWithDefaultLocale(t *testing.T) {
	loadMessages(testDataPath)
	loadTestI18nConfig(t)

	if message := Message("doesn't exist", "greeting"); message != "Hello" {
		t.Errorf("Expected message '%s' for unknown locale to be default '%s' but was '%s'", "greeting", "Hello", message)
	}
	if message := Message("doesn't exist", "unknown message"); message != "??? unknown message ???" {
		t.Error("Message 'unknown message' is not supposed to exist in the default language")
	}
}

func TestHasLocaleCookie(t *testing.T) {
	loadTestI18nConfig(t)

	if found, value := hasLocaleCookie(buildRequestWithCookie("APP_LANG", "en")); !found {
		t.Errorf("Expected %s cookie with value '%s' but found nothing or unexpected value '%s'", "APP_LANG", "en", value)
	}
	if found, value := hasLocaleCookie(buildRequestWithCookie("APP_LANG", "en-US")); !found {
		t.Errorf("Expected %s cookie with value '%s' but found nothing or unexpected value '%s'", "APP_LANG", "en-US", value)
	}
	if found, _ := hasLocaleCookie(buildRequestWithCookie("DOESNT_EXIST", "en-US")); found {
		t.Errorf("Expected %s cookie to not exist, but apparently it does", "DOESNT_EXIST")
	}
}

func TestHasLocaleCookieWithInvalidConfig(t *testing.T) {
	loadTestI18nConfigWithoutLanguageCookieOption(t)
	if found, _ := hasLocaleCookie(buildRequestWithCookie("APP_LANG", "en-US")); found {
		t.Errorf("Expected %s cookie to not exist because the configured name is missing", "APP_LANG")
	}
	if found, _ := hasLocaleCookie(buildRequestWithCookie("REVEL_LANG", "en-US")); !found {
		t.Errorf("Expected %s cookie to exist", "REVEL_LANG")
	}
}

func TestHasAcceptLanguageHeader(t *testing.T) {
	if found, value := hasAcceptLanguageHeader(buildRequestWithAcceptLanguages("en-US")); !found && value != "en-US" {
		t.Errorf("Expected to find Accept-Language header with value '%s', found '%s' instead", "en-US", value)
	}
	if found, value := hasAcceptLanguageHeader(buildRequestWithAcceptLanguages("en-GB", "en-US", "nl")); !found && value != "en-GB" {
		t.Errorf("Expected to find Accept-Language header with value '%s', found '%s' instead", "en-GB", value)
	}
}

func TestBeforeRequest(t *testing.T) {
	loadTestI18nConfig(t)

	c := NewController(buildEmptyRequest(), nil)
	if I18nFilter(c, NilChain); c.Request.Locale != "" {
		t.Errorf("Expected to find current language '%s' in controller, found '%s' instead", "", c.Request.Locale)
	}

	c = NewController(buildRequestWithCookie("APP_LANG", "en-US"), nil)
	if I18nFilter(c, NilChain); c.Request.Locale != "en-US" {
		t.Errorf("Expected to find current language '%s' in controller, found '%s' instead", "en-US", c.Request.Locale)
	}

	c = NewController(buildRequestWithAcceptLanguages("en-GB", "en-US"), nil)
	if I18nFilter(c, NilChain); c.Request.Locale != "en-GB" {
		t.Errorf("Expected to find current language '%s' in controller, found '%s' instead", "en-GB", c.Request.Locale)
	}
}

func TestI18nMessageUnknownValueFormat(t *testing.T) {
	loadMessages(testDataPath)
	loadTestI18nConfigWithUnknowFormatOption(t)

	// Assert that we can get a message and we get the expected return value
	if message := Message("en", "greeting"); message != "Hello" {
		t.Errorf("Message 'greeting' for locale 'en' (%s) does not have the expected value", message)
	}

	// Assert that we get the expected return value with original format
	if message := Message("unknown locale", "message"); message != "*** message ***" {
		t.Error("Locale 'unknown locale' is not supposed to exist")
	}
	if message := Message("nl", "unknown message"); message != "*** unknown message ***" {
		t.Error("Message 'unknown message' is not supposed to exist")
	}
}

func BenchmarkI18nLoadMessages(b *testing.B) {
	excludeFromTimer(b, func() { TRACE = log.New(ioutil.Discard, "", 0) })

	for i := 0; i < b.N; i++ {
		loadMessages(testDataPath)
	}
}

func BenchmarkI18nMessage(b *testing.B) {
	for i := 0; i < b.N; i++ {
		Message("nl", "greeting")
	}
}

func BenchmarkI18nMessageWithArguments(b *testing.B) {
	excludeFromTimer(b, func() { TRACE = log.New(ioutil.Discard, "", 0) })

	for i := 0; i < b.N; i++ {
		Message("en", "arguments.string", "Vincent Hanna")
	}
}

func BenchmarkI18nMessageWithFoldingAndArguments(b *testing.B) {
	excludeFromTimer(b, func() { TRACE = log.New(ioutil.Discard, "", 0) })

	for i := 0; i < b.N; i++ {
		Message("en", "folded.arguments", 12345)
	}
}

// Exclude whatever operations the given function performs from the benchmark timer.
func excludeFromTimer(b *testing.B, f func()) {
	b.StopTimer()
	f()
	b.StartTimer()
}

func loadTestI18nConfig(t *testing.T) {
	ConfPaths = append(ConfPaths, testConfigPath)
	testConfig, err := config.LoadContext(testConfigName, ConfPaths)
	if err != nil {
		t.Fatalf("Unable to load test config '%s': %s", testConfigName, err.Error())
	}
	Config = testConfig
	CookiePrefix = Config.StringDefault("cookie.prefix", "REVEL")
}

func loadTestI18nConfigWithoutLanguageCookieOption(t *testing.T) {
	loadTestI18nConfig(t)
	Config.Raw().RemoveOption("DEFAULT", "i18n.cookie")
}

func loadTestI18nConfigWithUnknowFormatOption(t *testing.T) {
	loadTestI18nConfig(t)
	Config.Raw().AddOption("DEFAULT", "i18n.unknown_format", "*** %s ***")
}

func buildRequestWithCookie(name, value string) *Request {
	httpRequest, _ := http.NewRequest("GET", "/", nil)
	request := NewRequest(httpRequest)
	request.AddCookie(&http.Cookie{
		Name:       name,
		Value:      value,
		Path:       "",
		Domain:     "",
		Expires:    time.Now(),
		RawExpires: "",
		MaxAge:     0,
		Secure:     false,
		HttpOnly:   false,
		Raw:        "",
		Unparsed:   nil,
	})
	return request
}

func buildRequestWithAcceptLanguages(acceptLanguages ...string) *Request {
	httpRequest, _ := http.NewRequest("GET", "/", nil)
	request := NewRequest(httpRequest)
	for _, acceptLanguage := range acceptLanguages {
		request.AcceptLanguages = append(request.AcceptLanguages, AcceptLanguage{acceptLanguage, 1})
	}
	return request
}

func buildEmptyRequest() *Request {
	httpRequest, _ := http.NewRequest("GET", "/", nil)
	request := NewRequest(httpRequest)
	return request
}
<|endoftext|>