<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"
	"time"

	"github.com/Songmu/timeout"
	"github.com/catatsuy/isucon6-final/bench/job"
	"github.com/catatsuy/isucon6-final/bench/score"
	"github.com/mitchellh/go-homedir"
)

// CLI is the command line object
type CLI struct {
	outStream, errStream io.Writer
}

func main() {
	cli := &CLI{outStream: os.Stdout, errStream: os.Stderr}
	os.Exit(cli.Run(os.Args[1:]))
}

const (
	exitCodeOK = iota
	exitCodeErr
)

func (cli *CLI) Run(args []string) int {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	log.SetOutput(cli.errStream)
	var (
		portalHost string
		benchPath  string
	)
	h, _ := homedir.Dir()
	defaultBenchPath := filepath.Join(h, "isucon6f", "isucon6f-bench")
	fs := flag.NewFlagSet("isu6q bench worker", flag.ContinueOnError)
	fs.SetOutput(cli.errStream)
	fs.StringVar(&portalHost, "portal", "isucon6q:phushohwaighai3fuphahchi@isucon6q.songmu.org", "portal Host")
	fs.StringVar(&benchPath, "bench", defaultBenchPath, "benchmark path")

	if err := fs.Parse(args); err != nil {
		return exitCodeErr
	}

	c := make(chan os.Signal)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGHUP)
	go func() {
		for range c {
			sigReceived = true
		}
	}()
	return cli.start(portalHost, benchPath)
}

type portal struct {
	host string
}

func (ptl *portal) newJobURL() string {
	return fmt.Sprintf("http://%s/mBGWHqBVEjUSKpBF/job/new", ptl.host)
}

func (ptl *portal) resultURL() string {
	return fmt.Sprintf("http://%s/mBGWHqBVEjUSKpBF/job/result", ptl.host)
}

var sigReceived bool

func (ptl *portal) waitJob() *job.Job {
	for !sigReceived {
		time.Sleep(3 * time.Second)
		j, err := ptl.fetchJob()
		if err != nil {
			log.Println(err)
			continue
		}
		if j != nil {
			return j
		}
	}
	return nil
}

func (ptl *portal) fetchJob() (*job.Job, error) {
	u := ptl.newJobURL()
	vals := url.Values{}
	h, _ := os.Hostname()
	vals.Set("bench_node", h)
	req, err := http.NewRequest(http.MethodPost, u, strings.NewReader(vals.Encode()))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	switch resp.StatusCode {
	case http.StatusOK:
		var j job.Job
		err = json.NewDecoder(resp.Body).Decode(&j)
		return &j, err
	case http.StatusNoContent:
		return nil, nil
	default:
		dump, _ := httputil.DumpResponse(resp, true)
		return nil, fmt.Errorf("response invalid: %s", string(dump))
	}
	return nil, nil
}

func (ptl *portal) postResult(res *job.Result) error {
	u := ptl.resultURL()
	json, _ := json.Marshal(res)

	req, err := http.NewRequest(http.MethodPost, u, bytes.NewBuffer([]byte(json)))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	return nil
}

func (cli *CLI) start(portalHost, benchPath string) int {
	ptl := &portal{host: portalHost}
	for !sigReceived {
		j := ptl.waitJob()
		if sigReceived {
			break
		}
		if j == nil {
			continue
		}
		log.Printf("received job: %#v\n", j)
		tio := buildBenchCmd(benchPath, j.IPAddr)
		_, out, stderr, err := tio.Run()
		if err != nil {
			log.Println(err)
		}
		if stderr != "" {
			log.Println(stderr)
		}
		log.Println("bench result: " + out)
		var o score.Output
		err = json.Unmarshal([]byte(out), &o)
		if err != nil {
			log.Printf("bench failed: %#v, err: %s\n", j, err.Error())
		}
		res := &job.Result{
			Job:    j,
			Output: &o,
		}
		err = ptl.postResult(res)
		if err != nil {
			log.Println(err)
		}
	}
	return exitCodeOK
}

func buildBenchCmd(benchPath, ipAddr string) *timeout.Timeout {
	cmd := exec.Command(benchPath, "-host", ipAddr, "-timeout", "30")
	cmd.Dir = filepath.Dir(benchPath)
	return &timeout.Timeout{
		Cmd:       cmd,
		Duration:  120 * time.Second,
		KillAfter: 5 * time.Second,
	}
}
<commit_msg>Change basic password<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"
	"time"

	"github.com/Songmu/timeout"
	"github.com/catatsuy/isucon6-final/bench/job"
	"github.com/catatsuy/isucon6-final/bench/score"
	"github.com/mitchellh/go-homedir"
)

// CLI is the command line object
type CLI struct {
	outStream, errStream io.Writer
}

func main() {
	cli := &CLI{outStream: os.Stdout, errStream: os.Stderr}
	os.Exit(cli.Run(os.Args[1:]))
}

const (
	exitCodeOK = iota
	exitCodeErr
)

func (cli *CLI) Run(args []string) int {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	log.SetOutput(cli.errStream)
	var (
		portalHost string
		benchPath  string
	)
	h, _ := homedir.Dir()
	defaultBenchPath := filepath.Join(h, "isucon6f", "isucon6f-bench")
	fs := flag.NewFlagSet("isu6q bench worker", flag.ContinueOnError)
	fs.SetOutput(cli.errStream)
	fs.StringVar(&portalHost, "portal", "isucon6f:rxmtquuhmrqwdygxzdakhnznuzghsrdz@isucon6q.songmu.org", "portal Host")
	fs.StringVar(&benchPath, "bench", defaultBenchPath, "benchmark path")

	if err := fs.Parse(args); err != nil {
		return exitCodeErr
	}

	c := make(chan os.Signal)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGHUP)
	go func() {
		for range c {
			sigReceived = true
		}
	}()
	return cli.start(portalHost, benchPath)
}

type portal struct {
	host string
}

func (ptl *portal) newJobURL() string {
	return fmt.Sprintf("http://%s/mBGWHqBVEjUSKpBF/job/new", ptl.host)
}

func (ptl *portal) resultURL() string {
	return fmt.Sprintf("http://%s/mBGWHqBVEjUSKpBF/job/result", ptl.host)
}

var sigReceived bool

func (ptl *portal) waitJob() *job.Job {
	for !sigReceived {
		time.Sleep(3 * time.Second)
		j, err := ptl.fetchJob()
		if err != nil {
			log.Println(err)
			continue
		}
		if j != nil {
			return j
		}
	}
	return nil
}

func (ptl *portal) fetchJob() (*job.Job, error) {
	u := ptl.newJobURL()
	vals := url.Values{}
	h, _ := os.Hostname()
	vals.Set("bench_node", h)
	req, err := http.NewRequest(http.MethodPost, u, strings.NewReader(vals.Encode()))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	switch resp.StatusCode {
	case http.StatusOK:
		var j job.Job
		err = json.NewDecoder(resp.Body).Decode(&j)
		return &j, err
	case http.StatusNoContent:
		return nil, nil
	default:
		dump, _ := httputil.DumpResponse(resp, true)
		return nil, fmt.Errorf("response invalid: %s", string(dump))
	}
	return nil, nil
}

func (ptl *portal) postResult(res *job.Result) error {
	u := ptl.resultURL()
	json, _ := json.Marshal(res)

	req, err := http.NewRequest(http.MethodPost, u, bytes.NewBuffer([]byte(json)))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	return nil
}

func (cli *CLI) start(portalHost, benchPath string) int {
	ptl := &portal{host: portalHost}
	for !sigReceived {
		j := ptl.waitJob()
		if sigReceived {
			break
		}
		if j == nil {
			continue
		}
		log.Printf("received job: %#v\n", j)
		tio := buildBenchCmd(benchPath, j.IPAddr)
		_, out, stderr, err := tio.Run()
		if err != nil {
			log.Println(err)
		}
		if stderr != "" {
			log.Println(stderr)
		}
		log.Println("bench result: " + out)
		var o score.Output
		err = json.Unmarshal([]byte(out), &o)
		if err != nil {
			log.Printf("bench failed: %#v, err: %s\n", j, err.Error())
		}
		res := &job.Result{
			Job:    j,
			Output: &o,
		}
		err = ptl.postResult(res)
		if err != nil {
			log.Println(err)
		}
	}
	return exitCodeOK
}

func buildBenchCmd(benchPath, ipAddr string) *timeout.Timeout {
	cmd := exec.Command(benchPath, "-host", ipAddr, "-timeout", "30")
	cmd.Dir = filepath.Dir(benchPath)
	return &timeout.Timeout{
		Cmd:       cmd,
		Duration:  120 * time.Second,
		KillAfter: 5 * time.Second,
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"time"

	recorder "github.com/docwhat/docker-gc/memrecorder"
)

type imageTagRecorder interface {
	SawImageTagAt(tag string, when time.Time)
}

func main() {
	fmt.Println("Press Control-C to exit...")
	tagger := recorder.NewMemRecorder()
	schedule(tagger)
	fmt.Printf("%v\n", tagger)
}

func schedule(tagger imageTagRecorder) {
	fmt.Printf("%v\n", tagger)
	tagger.SawImageTagAt("busybox", time.Now())
}
<commit_msg>more hacking around<commit_after>package main

import (
	"fmt"
	"log"
	"time"

	recorder "github.com/docwhat/docker-gc/memrecorder"
	dockerclient "github.com/fsouza/go-dockerclient"
)

type imageTagRecorder interface {
	SawImageTagAt(tag string, when time.Time)
}

func main() {
	fmt.Println("Press Control-C to exit...")
	tagger := recorder.NewMemRecorder()
	schedule(tagger)
}

func schedule(tagger imageTagRecorder) {
	client := newClient()

	listener := make(chan *dockerclient.APIEvents)
	if err := client.AddEventListener(listener); err != nil {
		log.Fatalf("Unable to listen for events: %s", err)
	}

	for {
		event := <-listener

		if event == nil {
			log.Fatalf("Lost connection to docker host")
		}

		switch event.Type {
		case "image":
			recordImage(tagger, event)
		case "container":
			recordContainer(tagger, event)
		case "network":
		default:
			log.Printf("Discarding (%s %s) %v", event.Action, event.Type, event.Actor.Attributes)
		}
	}
}

func recordImage(tagger imageTagRecorder, event *dockerclient.APIEvents) {
	if tagName, ok := event.Actor.Attributes["name"]; ok {
		name := normalizeRepoTag(tagName)
		time := time.Unix(event.Time, event.TimeNano)
		log.Printf("Image:     %8s %s", event.Action, name)
		tagger.SawImageTagAt(name, time)
	}
}

func recordContainer(tagger imageTagRecorder, event *dockerclient.APIEvents) {
	if tagName, ok := event.Actor.Attributes["image"]; ok {
		name := normalizeRepoTag(tagName)
		time := time.Unix(event.Time, event.TimeNano)
		log.Printf("Container: %8s %s", event.Action, name)
		tagger.SawImageTagAt(name, time)
	}
}

func normalizeRepoTag(repoTag string) string {
	repo, tag := dockerclient.ParseRepositoryTag(repoTag)
	if tag == "" {
		tag = "latest"
	}
	return fmt.Sprintf("%s:%s", repo, tag)
}

func newClient() *dockerclient.Client {
	client, err := dockerclient.NewClientFromEnv()
	if err != nil {
		log.Fatalf("Unable to create docker client: %s", err)
	}

	if err := client.Ping(); err != nil {
		log.Printf("Cannot connect to the Docker daemon. Is the docker daemon running on this host?")
		log.Fatalf("%s", err)
	}
	return client
}
<|endoftext|>


<commit_before>package v1

import (
	"sync"
	"testing"
	"time"

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

	"github.com/tendermint/tendermint/libs/log"
	tmrand "github.com/tendermint/tendermint/libs/rand"
	"github.com/tendermint/tendermint/p2p"
	"github.com/tendermint/tendermint/types"
)

func TestPeerMonitor(t *testing.T) {
	peer := NewBpPeer(
		p2p.ID(tmrand.Str(12)), 0, 10,
		func(err error, _ p2p.ID) {},
		nil)
	peer.SetLogger(log.TestingLogger())
	peer.startMonitor()
	assert.NotNil(t, peer.recvMonitor)
	peer.stopMonitor()
	assert.Nil(t, peer.recvMonitor)
}

func TestPeerResetBlockResponseTimer(t *testing.T) {
	var (
		numErrFuncCalls int        // number of calls to the errFunc
		lastErr         error      // last generated error
		peerTestMtx     sync.Mutex // modifications of ^^ variables are also done from timer handler goroutine
	)
	params := &BpPeerParams{timeout: 2 * time.Millisecond}

	peer := NewBpPeer(
		p2p.ID(tmrand.Str(12)), 0, 10,
		func(err error, _ p2p.ID) {
			peerTestMtx.Lock()
			defer peerTestMtx.Unlock()
			lastErr = err
			numErrFuncCalls++
		},
		params)

	peer.SetLogger(log.TestingLogger())
	checkByStoppingPeerTimer(t, peer, false)

	// initial reset call with peer having a nil timer
	peer.resetBlockResponseTimer()
	assert.NotNil(t, peer.blockResponseTimer)
	// make sure timer is running and stop it
	checkByStoppingPeerTimer(t, peer, true)

	// reset with running timer
	peer.resetBlockResponseTimer()
	time.Sleep(time.Millisecond)
	peer.resetBlockResponseTimer()
	assert.NotNil(t, peer.blockResponseTimer)

	// let the timer expire and ...
	time.Sleep(3 * time.Millisecond)
	// ... check timer is not running
	checkByStoppingPeerTimer(t, peer, false)

	peerTestMtx.Lock()
	// ... check errNoPeerResponse has been sent
	assert.Equal(t, 1, numErrFuncCalls)
	assert.Equal(t, lastErr, errNoPeerResponse)
	peerTestMtx.Unlock()
}

func TestPeerRequestSent(t *testing.T) {
	params := &BpPeerParams{timeout: 2 * time.Millisecond}

	peer := NewBpPeer(
		p2p.ID(tmrand.Str(12)), 0, 10,
		func(err error, _ p2p.ID) {},
		params)

	peer.SetLogger(log.TestingLogger())

	peer.RequestSent(1)
	assert.NotNil(t, peer.recvMonitor)
	assert.NotNil(t, peer.blockResponseTimer)
	assert.Equal(t, 1, peer.NumPendingBlockRequests)

	peer.RequestSent(1)
	assert.NotNil(t, peer.recvMonitor)
	assert.NotNil(t, peer.blockResponseTimer)
	assert.Equal(t, 2, peer.NumPendingBlockRequests)
}

func TestPeerGetAndRemoveBlock(t *testing.T) {
	peer := NewBpPeer(
		p2p.ID(tmrand.Str(12)), 0, 100,
		func(err error, _ p2p.ID) {},
		nil)

	// Change peer height
	peer.Height = int64(10)
	assert.Equal(t, int64(10), peer.Height)

	// request some blocks and receive few of them
	for i := 1; i <= 10; i++ {
		peer.RequestSent(int64(i))
		if i > 5 {
			// only receive blocks 1..5
			continue
		}
		_ = peer.AddBlock(makeSmallBlock(i), 10)
	}

	tests := []struct {
		name         string
		height       int64
		wantErr      error
		blockPresent bool
	}{
		{"no request", 100, errMissingBlock, false},
		{"no block", 6, errMissingBlock, false},
		{"block 1 present", 1, nil, true},
		{"block max present", 5, nil, true},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			// try to get the block
			b, err := peer.BlockAtHeight(tt.height)
			assert.Equal(t, tt.wantErr, err)
			assert.Equal(t, tt.blockPresent, b != nil)

			// remove the block
			peer.RemoveBlock(tt.height)
			_, err = peer.BlockAtHeight(tt.height)
			assert.Equal(t, errMissingBlock, err)
		})
	}
}

func TestPeerAddBlock(t *testing.T) {
	peer := NewBpPeer(
		p2p.ID(tmrand.Str(12)), 0, 100,
		func(err error, _ p2p.ID) {},
		nil)

	// request some blocks, receive one
	for i := 1; i <= 10; i++ {
		peer.RequestSent(int64(i))
		if i == 5 {
			// receive block 5
			_ = peer.AddBlock(makeSmallBlock(i), 10)
		}
	}

	tests := []struct {
		name         string
		height       int64
		wantErr      error
		blockPresent bool
	}{
		{"no request", 50, errMissingBlock, false},
		{"duplicate block", 5, errDuplicateBlock, true},
		{"block 1 successfully received", 1, nil, true},
		{"block max successfully received", 10, nil, true},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			// try to get the block
			err := peer.AddBlock(makeSmallBlock(int(tt.height)), 10)
			assert.Equal(t, tt.wantErr, err)
			_, err = peer.BlockAtHeight(tt.height)
			assert.Equal(t, tt.blockPresent, err == nil)
		})
	}
}

func TestPeerOnErrFuncCalledDueToExpiration(t *testing.T) {

	params := &BpPeerParams{timeout: 2 * time.Millisecond}
	var (
		numErrFuncCalls int        // number of calls to the onErr function
		lastErr         error      // last generated error
		peerTestMtx     sync.Mutex // modifications of ^^ variables are also done from timer handler goroutine
	)

	peer := NewBpPeer(
		p2p.ID(tmrand.Str(12)), 0, 10,
		func(err error, _ p2p.ID) {
			peerTestMtx.Lock()
			defer peerTestMtx.Unlock()
			lastErr = err
			numErrFuncCalls++
		},
		params)

	peer.SetLogger(log.TestingLogger())

	peer.RequestSent(1)
	time.Sleep(4 * time.Millisecond)
	// timer should have expired by now, check that the on error function was called
	peerTestMtx.Lock()
	assert.Equal(t, 1, numErrFuncCalls)
	assert.Equal(t, errNoPeerResponse, lastErr)
	peerTestMtx.Unlock()
}

func TestPeerCheckRate(t *testing.T) {
	params := &BpPeerParams{
		timeout:     time.Second,
		minRecvRate: int64(100), // 100 bytes/sec exponential moving average
	}
	peer := NewBpPeer(
		p2p.ID(tmrand.Str(12)), 0, 10,
		func(err error, _ p2p.ID) {},
		params)
	peer.SetLogger(log.TestingLogger())

	require.Nil(t, peer.CheckRate())

	for i := 0; i < 40; i++ {
		peer.RequestSent(int64(i))
	}

	// monitor starts with a higher rEMA (~ 2*minRecvRate), wait for it to go down
	time.Sleep(900 * time.Millisecond)

	// normal peer - send a bit more than 100 bytes/sec, > 10 bytes/100msec, check peer is not considered slow
	for i := 0; i < 10; i++ {
		_ = peer.AddBlock(makeSmallBlock(i), 11)
		time.Sleep(100 * time.Millisecond)
		require.Nil(t, peer.CheckRate())
	}

	// slow peer - send a bit less than 10 bytes/100msec
	for i := 10; i < 20; i++ {
		_ = peer.AddBlock(makeSmallBlock(i), 9)
		time.Sleep(100 * time.Millisecond)
	}
	// check peer is considered slow
	assert.Equal(t, errSlowPeer, peer.CheckRate())
}

func TestPeerCleanup(t *testing.T) {
	params := &BpPeerParams{timeout: 2 * time.Millisecond}

	peer := NewBpPeer(
		p2p.ID(tmrand.Str(12)), 0, 10,
		func(err error, _ p2p.ID) {},
		params)
	peer.SetLogger(log.TestingLogger())

	assert.Nil(t, peer.blockResponseTimer)
	peer.RequestSent(1)
	assert.NotNil(t, peer.blockResponseTimer)

	peer.Cleanup()
	checkByStoppingPeerTimer(t, peer, false)
}

// Check if peer timer is running or not (a running timer can be successfully stopped).
// Note: stops the timer.
func checkByStoppingPeerTimer(t *testing.T, peer *BpPeer, running bool) {
	assert.NotPanics(t, func() {
		stopped := peer.stopBlockResponseTimer()
		if running {
			assert.True(t, stopped)
		} else {
			assert.False(t, stopped)
		}
	})
}

func makeSmallBlock(height int) *types.Block {
	return types.MakeBlock(int64(height), []types.Tx{types.Tx("foo")}, nil, nil)
}
<commit_msg>blockchain[v1]: increased timeout times for peer tests (#4871)<commit_after>package v1

import (
	"sync"
	"testing"
	"time"

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

	"github.com/tendermint/tendermint/libs/log"
	tmrand "github.com/tendermint/tendermint/libs/rand"
	"github.com/tendermint/tendermint/p2p"
	"github.com/tendermint/tendermint/types"
)

func TestPeerMonitor(t *testing.T) {
	peer := NewBpPeer(
		p2p.ID(tmrand.Str(12)), 0, 10,
		func(err error, _ p2p.ID) {},
		nil)
	peer.SetLogger(log.TestingLogger())
	peer.startMonitor()
	assert.NotNil(t, peer.recvMonitor)
	peer.stopMonitor()
	assert.Nil(t, peer.recvMonitor)
}

func TestPeerResetBlockResponseTimer(t *testing.T) {
	var (
		numErrFuncCalls int        // number of calls to the errFunc
		lastErr         error      // last generated error
		peerTestMtx     sync.Mutex // modifications of ^^ variables are also done from timer handler goroutine
	)
	params := &BpPeerParams{timeout: 20 * time.Millisecond}

	peer := NewBpPeer(
		p2p.ID(tmrand.Str(12)), 0, 10,
		func(err error, _ p2p.ID) {
			peerTestMtx.Lock()
			defer peerTestMtx.Unlock()
			lastErr = err
			numErrFuncCalls++
		},
		params)

	peer.SetLogger(log.TestingLogger())
	checkByStoppingPeerTimer(t, peer, false)

	// initial reset call with peer having a nil timer
	peer.resetBlockResponseTimer()
	assert.NotNil(t, peer.blockResponseTimer)
	// make sure timer is running and stop it
	checkByStoppingPeerTimer(t, peer, true)

	// reset with running timer
	peer.resetBlockResponseTimer()
	time.Sleep(5 * time.Millisecond)
	peer.resetBlockResponseTimer()
	assert.NotNil(t, peer.blockResponseTimer)

	// let the timer expire and ...
	time.Sleep(50 * time.Millisecond)
	// ... check timer is not running
	checkByStoppingPeerTimer(t, peer, false)

	peerTestMtx.Lock()
	// ... check errNoPeerResponse has been sent
	assert.Equal(t, 1, numErrFuncCalls)
	assert.Equal(t, lastErr, errNoPeerResponse)
	peerTestMtx.Unlock()
}

func TestPeerRequestSent(t *testing.T) {
	params := &BpPeerParams{timeout: 2 * time.Millisecond}

	peer := NewBpPeer(
		p2p.ID(tmrand.Str(12)), 0, 10,
		func(err error, _ p2p.ID) {},
		params)

	peer.SetLogger(log.TestingLogger())

	peer.RequestSent(1)
	assert.NotNil(t, peer.recvMonitor)
	assert.NotNil(t, peer.blockResponseTimer)
	assert.Equal(t, 1, peer.NumPendingBlockRequests)

	peer.RequestSent(1)
	assert.NotNil(t, peer.recvMonitor)
	assert.NotNil(t, peer.blockResponseTimer)
	assert.Equal(t, 2, peer.NumPendingBlockRequests)
}

func TestPeerGetAndRemoveBlock(t *testing.T) {
	peer := NewBpPeer(
		p2p.ID(tmrand.Str(12)), 0, 100,
		func(err error, _ p2p.ID) {},
		nil)

	// Change peer height
	peer.Height = int64(10)
	assert.Equal(t, int64(10), peer.Height)

	// request some blocks and receive few of them
	for i := 1; i <= 10; i++ {
		peer.RequestSent(int64(i))
		if i > 5 {
			// only receive blocks 1..5
			continue
		}
		_ = peer.AddBlock(makeSmallBlock(i), 10)
	}

	tests := []struct {
		name         string
		height       int64
		wantErr      error
		blockPresent bool
	}{
		{"no request", 100, errMissingBlock, false},
		{"no block", 6, errMissingBlock, false},
		{"block 1 present", 1, nil, true},
		{"block max present", 5, nil, true},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			// try to get the block
			b, err := peer.BlockAtHeight(tt.height)
			assert.Equal(t, tt.wantErr, err)
			assert.Equal(t, tt.blockPresent, b != nil)

			// remove the block
			peer.RemoveBlock(tt.height)
			_, err = peer.BlockAtHeight(tt.height)
			assert.Equal(t, errMissingBlock, err)
		})
	}
}

func TestPeerAddBlock(t *testing.T) {
	peer := NewBpPeer(
		p2p.ID(tmrand.Str(12)), 0, 100,
		func(err error, _ p2p.ID) {},
		nil)

	// request some blocks, receive one
	for i := 1; i <= 10; i++ {
		peer.RequestSent(int64(i))
		if i == 5 {
			// receive block 5
			_ = peer.AddBlock(makeSmallBlock(i), 10)
		}
	}

	tests := []struct {
		name         string
		height       int64
		wantErr      error
		blockPresent bool
	}{
		{"no request", 50, errMissingBlock, false},
		{"duplicate block", 5, errDuplicateBlock, true},
		{"block 1 successfully received", 1, nil, true},
		{"block max successfully received", 10, nil, true},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			// try to get the block
			err := peer.AddBlock(makeSmallBlock(int(tt.height)), 10)
			assert.Equal(t, tt.wantErr, err)
			_, err = peer.BlockAtHeight(tt.height)
			assert.Equal(t, tt.blockPresent, err == nil)
		})
	}
}

func TestPeerOnErrFuncCalledDueToExpiration(t *testing.T) {

	params := &BpPeerParams{timeout: 10 * time.Millisecond}
	var (
		numErrFuncCalls int        // number of calls to the onErr function
		lastErr         error      // last generated error
		peerTestMtx     sync.Mutex // modifications of ^^ variables are also done from timer handler goroutine
	)

	peer := NewBpPeer(
		p2p.ID(tmrand.Str(12)), 0, 10,
		func(err error, _ p2p.ID) {
			peerTestMtx.Lock()
			defer peerTestMtx.Unlock()
			lastErr = err
			numErrFuncCalls++
		},
		params)

	peer.SetLogger(log.TestingLogger())

	peer.RequestSent(1)
	time.Sleep(50 * time.Millisecond)
	// timer should have expired by now, check that the on error function was called
	peerTestMtx.Lock()
	assert.Equal(t, 1, numErrFuncCalls)
	assert.Equal(t, errNoPeerResponse, lastErr)
	peerTestMtx.Unlock()
}

func TestPeerCheckRate(t *testing.T) {
	params := &BpPeerParams{
		timeout:     time.Second,
		minRecvRate: int64(100), // 100 bytes/sec exponential moving average
	}
	peer := NewBpPeer(
		p2p.ID(tmrand.Str(12)), 0, 10,
		func(err error, _ p2p.ID) {},
		params)
	peer.SetLogger(log.TestingLogger())

	require.Nil(t, peer.CheckRate())

	for i := 0; i < 40; i++ {
		peer.RequestSent(int64(i))
	}

	// monitor starts with a higher rEMA (~ 2*minRecvRate), wait for it to go down
	time.Sleep(900 * time.Millisecond)

	// normal peer - send a bit more than 100 bytes/sec, > 10 bytes/100msec, check peer is not considered slow
	for i := 0; i < 10; i++ {
		_ = peer.AddBlock(makeSmallBlock(i), 11)
		time.Sleep(100 * time.Millisecond)
		require.Nil(t, peer.CheckRate())
	}

	// slow peer - send a bit less than 10 bytes/100msec
	for i := 10; i < 20; i++ {
		_ = peer.AddBlock(makeSmallBlock(i), 9)
		time.Sleep(100 * time.Millisecond)
	}
	// check peer is considered slow
	assert.Equal(t, errSlowPeer, peer.CheckRate())
}

func TestPeerCleanup(t *testing.T) {
	params := &BpPeerParams{timeout: 2 * time.Millisecond}

	peer := NewBpPeer(
		p2p.ID(tmrand.Str(12)), 0, 10,
		func(err error, _ p2p.ID) {},
		params)
	peer.SetLogger(log.TestingLogger())

	assert.Nil(t, peer.blockResponseTimer)
	peer.RequestSent(1)
	assert.NotNil(t, peer.blockResponseTimer)

	peer.Cleanup()
	checkByStoppingPeerTimer(t, peer, false)
}

// Check if peer timer is running or not (a running timer can be successfully stopped).
// Note: stops the timer.
func checkByStoppingPeerTimer(t *testing.T, peer *BpPeer, running bool) {
	assert.NotPanics(t, func() {
		stopped := peer.stopBlockResponseTimer()
		if running {
			assert.True(t, stopped)
		} else {
			assert.False(t, stopped)
		}
	})
}

func makeSmallBlock(height int) *types.Block {
	return types.MakeBlock(int64(height), []types.Tx{types.Tx("foo")}, nil, nil)
}
<|endoftext|>


<commit_before>/*
Copyright 2019 Google LLC

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 e2e

import (
	"cloud.google.com/go/pubsub"
	"context"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"knative.dev/pkg/test/helpers"
	"os"
	"testing"
	"time"

	// Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters).
	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
)

const (
	ProwProjectKey = "E2E_PROJECT_ID"
)

func makeTopicOrDie(t *testing.T) (string, func()) {
	ctx := context.Background()
	// Prow sticks teh project in this key
	project := os.Getenv(ProwProjectKey)
	if project == "" {
		t.Fatalf("failed to find %q in envvars", ProwProjectKey)
	}
	client, err := pubsub.NewClient(ctx, project)
	if err != nil {
		t.Fatalf("failed to create pubsub client, %s", err.Error())
	}
	topicName := helpers.AppendRandomString("ps-e2e-test")
	topic := client.Topic(topicName)
	if exists, err := topic.Exists(ctx); err != nil {
		t.Fatalf("failed to verify topic exists, %s", err)
	} else if exists {
		t.Fatalf("topic already exists: %q", topicName)
	} else {
		topic, err = client.CreateTopic(ctx, topicName)
		if err != nil {
			t.Fatalf("failed to create topic, %s", err)
		}
	}
	return topicName, func() {
		deleteTopicOrDie(t, topicName)
	}
}

func deleteTopicOrDie(t *testing.T, topicName string) {
	ctx := context.Background()
	// Prow sticks the project in this key.
	project := os.Getenv(ProwProjectKey)
	if project == "" {
		t.Fatalf("failed to find %q in envvars", ProwProjectKey)
	}
	client, err := pubsub.NewClient(ctx, project)
	if err != nil {
		t.Fatalf("failed to create pubsub client, %s", err.Error())
	}
	topic := client.Topic(topicName)
	if exists, err := topic.Exists(ctx); err != nil {
		t.Fatalf("failed to verify topic exists, %s", err)
	} else if exists {
		if err := topic.Delete(ctx); err != nil {
			t.Fatalf("failed to delete topic, %s", err)
		}
	}
}

// SmokePullSubscriptionTestImpl tests we can create a pull subscription to ready state.
func SmokePullSubscriptionTestImpl(t *testing.T) {
	topic, deleteTopic := makeTopicOrDie(t)
	defer deleteTopic()

	psName := topic + "-sub"

	client := Setup(t, true)
	defer TearDown(client)

	installer := NewInstaller(client.Dynamic, map[string]string{
		"namespace":    client.Namespace,
		"topic":        topic,
		"subscription": psName,
	}, EndToEndConfigYaml([]string{"pull_subscription_test", "istio", "event_display"})...)

	// Create the resources for the test.
	if err := installer.Do("create"); err != nil {
		t.Errorf("failed to create, %s", err)
		return
	}

	// Delete deferred.
	defer func() {
		// Just chill for tick.
		time.Sleep(10 * time.Second)
		if err := installer.Do("delete"); err != nil {
			t.Errorf("failed to create, %s", err)
		}
	}()

	if err := client.WaitForResourceReady(client.Namespace, psName, schema.GroupVersionResource{
		Group:    "pubsub.cloud.run",
		Version:  "v1alpha1",
		Resource: "pullsubscriptions",
	}); err != nil {
		t.Error(err)
	}
}
<commit_msg>Fix spelling errors<commit_after>/*
Copyright 2019 Google LLC

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 e2e

import (
	"cloud.google.com/go/pubsub"
	"context"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"knative.dev/pkg/test/helpers"
	"os"
	"testing"
	"time"

	// Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters).
	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
)

const (
	ProwProjectKey = "E2E_PROJECT_ID"
)

func makeTopicOrDie(t *testing.T) (string, func()) {
	ctx := context.Background()
	// Prow sticks the project in this key
	project := os.Getenv(ProwProjectKey)
	if project == "" {
		t.Fatalf("failed to find %q in envvars", ProwProjectKey)
	}
	client, err := pubsub.NewClient(ctx, project)
	if err != nil {
		t.Fatalf("failed to create pubsub client, %s", err.Error())
	}
	topicName := helpers.AppendRandomString("ps-e2e-test")
	topic := client.Topic(topicName)
	if exists, err := topic.Exists(ctx); err != nil {
		t.Fatalf("failed to verify topic exists, %s", err)
	} else if exists {
		t.Fatalf("topic already exists: %q", topicName)
	} else {
		topic, err = client.CreateTopic(ctx, topicName)
		if err != nil {
			t.Fatalf("failed to create topic, %s", err)
		}
	}
	return topicName, func() {
		deleteTopicOrDie(t, topicName)
	}
}

func deleteTopicOrDie(t *testing.T, topicName string) {
	ctx := context.Background()
	// Prow sticks the project in this key.
	project := os.Getenv(ProwProjectKey)
	if project == "" {
		t.Fatalf("failed to find %q in envvars", ProwProjectKey)
	}
	client, err := pubsub.NewClient(ctx, project)
	if err != nil {
		t.Fatalf("failed to create pubsub client, %s", err.Error())
	}
	topic := client.Topic(topicName)
	if exists, err := topic.Exists(ctx); err != nil {
		t.Fatalf("failed to verify topic exists, %s", err)
	} else if exists {
		if err := topic.Delete(ctx); err != nil {
			t.Fatalf("failed to delete topic, %s", err)
		}
	}
}

// SmokePullSubscriptionTestImpl tests we can create a pull subscription to ready state.
func SmokePullSubscriptionTestImpl(t *testing.T) {
	topic, deleteTopic := makeTopicOrDie(t)
	defer deleteTopic()

	psName := topic + "-sub"

	client := Setup(t, true)
	defer TearDown(client)

	installer := NewInstaller(client.Dynamic, map[string]string{
		"namespace":    client.Namespace,
		"topic":        topic,
		"subscription": psName,
	}, EndToEndConfigYaml([]string{"pull_subscription_test", "istio", "event_display"})...)

	// Create the resources for the test.
	if err := installer.Do("create"); err != nil {
		t.Errorf("failed to create, %s", err)
		return
	}

	// Delete deferred.
	defer func() {
		// Just chill for tick.
		time.Sleep(10 * time.Second)
		if err := installer.Do("delete"); err != nil {
			t.Errorf("failed to create, %s", err)
		}
	}()

	if err := client.WaitForResourceReady(client.Namespace, psName, schema.GroupVersionResource{
		Group:    "pubsub.cloud.run",
		Version:  "v1alpha1",
		Resource: "pullsubscriptions",
	}); err != nil {
		t.Error(err)
	}
}
<|endoftext|>


<commit_before>package fire

import (
	"errors"
	"reflect"

	"gopkg.in/mgo.v2/bson"
)

// A Callback is called during execution of a controller.
//
// Note: If the callback returns an error wrapped using Fatal() the API returns
// an InternalServerError status and the error will be logged. All other errors
// are serialized to an error object and returned.
type Callback func(*Context) error

type fatalError struct {
	err error
}

// Fatal wraps an error and marks it as fatal.
func Fatal(err error) error {
	return &fatalError{
		err: err,
	}
}

func (err *fatalError) Error() string {
	return err.err.Error()
}

func isFatal(err error) bool {
	_, ok := err.(*fatalError)
	return ok
}

// Only will return a callback that runs the specified callback only when one
// of the supplied actions match.
func Only(cb Callback, actions ...Action) Callback {
	return func(ctx *Context) error {
		// check action
		for _, a := range actions {
			if a == ctx.Action {
				return cb(ctx)
			}
		}

		return nil
	}
}

// Except will return a callback that runs the specified callback only when none
// of the supplied actions match.
func Except(cb Callback, actions ...Action) Callback {
	return func(ctx *Context) error {
		// check action
		for _, a := range actions {
			if a == ctx.Action {
				return nil
			}
		}

		return cb(ctx)
	}
}

// ModelValidator performs a validation of the model using the Validate function.
func ModelValidator() Callback {
	return func(ctx *Context) error {
		// only run validator on Create and Update
		if ctx.Action != Create && ctx.Action != Update {
			return nil
		}

		// TODO: Add error source pointer.

		return Validate(ctx.Model)
	}
}

// ProtectedAttributesValidator compares protected attributes against their
// default (during Create) or stored value (during Update) and returns and
// error if they have been changed.
//
// Attributes are defined by passing pairs of fields and default values:
//
//		ProtectedAttributesValidator(map[string]interface{}{
//			"title": "A fixed title",
//		})
//
func ProtectedAttributesValidator(attributes map[string]interface{}) Callback {
	return func(ctx *Context) error {
		// only run validator on Create and Update
		if ctx.Action != Create && ctx.Action != Update {
			return nil
		}

		if ctx.Action == Create {
			// check all attributes
			for field, def := range attributes {
				if !reflect.DeepEqual(ctx.Model.MustGet(field), def) {
					return errors.New("Field " + field + " is protected")
				}
			}
		}

		if ctx.Action == Update {
			// read the original
			original, err := ctx.Original()
			if err != nil {
				return err
			}

			// check all attributes
			for field := range attributes {
				if !reflect.DeepEqual(ctx.Model.MustGet(field), original.MustGet(field)) {
					return errors.New("Field " + field + " is protected")
				}
			}
		}

		return nil
	}
}

// DependentResourcesValidator counts documents in the supplied collections
// and returns an error if some get found. This callback is meant to protect
// resources from breaking relations when requested to be deleted.
//
// Resources are defined by passing pairs of collections and fields where the
// field must be a database field of the target resource model:
//
//		DependentResourcesValidator(map[string]string{
// 			"posts": "user_id",
//			"comments": "user_id",
// 		})
//
func DependentResourcesValidator(resources map[string]string) Callback {
	return func(ctx *Context) error {
		// only run validator on Delete
		if ctx.Action != Delete {
			return nil
		}

		// check all relations
		for coll, field := range resources {
			// count referencing documents
			n, err := ctx.Store.DB().C(coll).Find(bson.M{
				field: ctx.Query["_id"],
			}).Limit(1).Count()
			if err != nil {
				return Fatal(err)
			}

			// immediately return if a document is found
			if n == 1 {
				return errors.New("Resource has dependent resources")
			}
		}

		// pass validation
		return nil
	}
}

// VerifyReferencesValidator makes sure all references in the document are
// existing by counting on the related collections.
//
// References are defined by passing pairs of fields and collections where the
// field must be a database field on the resource model:
//
//		VerifyReferencesValidator(map[string]string{
// 			"post_id": "posts",
//			"user_id": "users",
// 		})
//
func VerifyReferencesValidator(references map[string]string) Callback {
	return func(ctx *Context) error {
		// only run validator on Create and Update
		if ctx.Action != Create && ctx.Action != Update {
			return nil
		}

		// check all references
		for field, collection := range references {
			// read referenced id
			id := ctx.Model.MustGet(field)

			// continue if reference is not set
			if oid, ok := id.(*bson.ObjectId); ok && oid == nil {
				continue
			}

			// count entities in database
			n, err := ctx.Store.DB().C(collection).FindId(id).Limit(1).Count()
			if err != nil {
				return Fatal(err)
			}

			// check for existence
			if n != 1 {
				return errors.New("Missing required relationship " + field)
			}
		}

		// pass validation
		return nil
	}
}

// MatchingReferencesValidator compares the model with a related model and
// checks if certain references are shared.
//
// The target model is defined by passing its collection and the referencing
// field on the current model. The matcher is defined by passing pairs of
// database fields on the target and current model:
//
//		MatchingReferencesValidator("posts", "post_id", map[string]string{
// 			"user_id": "user_id",
// 		})
//
func MatchingReferencesValidator(collection, reference string, matcher map[string]string) Callback {
	return func(ctx *Context) error {
		// only run validator on Create and Update
		if ctx.Action != Create && ctx.Action != Update {
			return nil
		}

		// get main reference
		id := ctx.Model.MustGet(reference)

		// continue if reference is not set
		if oid, ok := id.(*bson.ObjectId); ok && oid == nil {
			return nil
		}

		// prepare query
		query := bson.M{
			"_id": id,
		}

		// add other references
		for targetField, modelField := range matcher {
			id := ctx.Model.MustGet(modelField)

			// abort if reference is missing
			if oid, ok := id.(*bson.ObjectId); ok && oid == nil {
				return errors.New("Missing ID")
			}

			query[targetField] = id
		}

		// query db
		n, err := ctx.Store.DB().C(collection).Find(query).Limit(1).Count()
		if err != nil {
			return Fatal(err)
		}

		// return error if document is missing
		if n == 0 {
			return errors.New("References do not match")
		}

		return nil
	}
}
<commit_msg>added UniqueAttributeValidator<commit_after>package fire

import (
	"errors"
	"fmt"
	"reflect"

	"gopkg.in/mgo.v2/bson"
)

// A Callback is called during execution of a controller.
//
// Note: If the callback returns an error wrapped using Fatal() the API returns
// an InternalServerError status and the error will be logged. All other errors
// are serialized to an error object and returned.
type Callback func(*Context) error

type fatalError struct {
	err error
}

// Fatal wraps an error and marks it as fatal.
func Fatal(err error) error {
	return &fatalError{
		err: err,
	}
}

func (err *fatalError) Error() string {
	return err.err.Error()
}

func isFatal(err error) bool {
	_, ok := err.(*fatalError)
	return ok
}

// Only will return a callback that runs the specified callback only when one
// of the supplied actions match.
func Only(cb Callback, actions ...Action) Callback {
	return func(ctx *Context) error {
		// check action
		for _, a := range actions {
			if a == ctx.Action {
				return cb(ctx)
			}
		}

		return nil
	}
}

// Except will return a callback that runs the specified callback only when none
// of the supplied actions match.
func Except(cb Callback, actions ...Action) Callback {
	return func(ctx *Context) error {
		// check action
		for _, a := range actions {
			if a == ctx.Action {
				return nil
			}
		}

		return cb(ctx)
	}
}

// ModelValidator performs a validation of the model using the Validate function.
func ModelValidator() Callback {
	return func(ctx *Context) error {
		// only run validator on Create and Update
		if ctx.Action != Create && ctx.Action != Update {
			return nil
		}

		// TODO: Add error source pointer.

		return Validate(ctx.Model)
	}
}

// ProtectedAttributesValidator compares protected attributes against their
// default (during Create) or stored value (during Update) and returns and
// error if they have been changed.
//
// Attributes are defined by passing pairs of fields and default values:
//
//		ProtectedAttributesValidator(map[string]interface{}{
//			"title": "A fixed title",
//		})
//
func ProtectedAttributesValidator(attributes map[string]interface{}) Callback {
	return func(ctx *Context) error {
		// only run validator on Create and Update
		if ctx.Action != Create && ctx.Action != Update {
			return nil
		}

		if ctx.Action == Create {
			// check all attributes
			for field, def := range attributes {
				if !reflect.DeepEqual(ctx.Model.MustGet(field), def) {
					return errors.New("Field " + field + " is protected")
				}
			}
		}

		if ctx.Action == Update {
			// read the original
			original, err := ctx.Original()
			if err != nil {
				return err
			}

			// check all attributes
			for field := range attributes {
				if !reflect.DeepEqual(ctx.Model.MustGet(field), original.MustGet(field)) {
					return errors.New("Field " + field + " is protected")
				}
			}
		}

		return nil
	}
}

// DependentResourcesValidator counts documents in the supplied collections
// and returns an error if some get found. This callback is meant to protect
// resources from breaking relations when requested to be deleted.
//
// Resources are defined by passing pairs of collections and fields where the
// field must be a database field of the target resource model:
//
//		DependentResourcesValidator(map[string]string{
// 			"posts": "user_id",
//			"comments": "user_id",
// 		})
//
func DependentResourcesValidator(resources map[string]string) Callback {
	return func(ctx *Context) error {
		// only run validator on Delete
		if ctx.Action != Delete {
			return nil
		}

		// check all relations
		for coll, field := range resources {
			// count referencing documents
			n, err := ctx.Store.DB().C(coll).Find(bson.M{
				field: ctx.Query["_id"],
			}).Limit(1).Count()
			if err != nil {
				return Fatal(err)
			}

			// immediately return if a document is found
			if n == 1 {
				return errors.New("Resource has dependent resources")
			}
		}

		// pass validation
		return nil
	}
}

// VerifyReferencesValidator makes sure all references in the document are
// existing by counting on the related collections.
//
// References are defined by passing pairs of fields and collections where the
// field must be a database field on the resource model:
//
//		VerifyReferencesValidator(map[string]string{
// 			"post_id": "posts",
//			"user_id": "users",
// 		})
//
func VerifyReferencesValidator(references map[string]string) Callback {
	return func(ctx *Context) error {
		// only run validator on Create and Update
		if ctx.Action != Create && ctx.Action != Update {
			return nil
		}

		// check all references
		for field, collection := range references {
			// read referenced id
			id := ctx.Model.MustGet(field)

			// continue if reference is not set
			if oid, ok := id.(*bson.ObjectId); ok && oid == nil {
				continue
			}

			// count entities in database
			n, err := ctx.Store.DB().C(collection).FindId(id).Limit(1).Count()
			if err != nil {
				return Fatal(err)
			}

			// check for existence
			if n != 1 {
				return errors.New("Missing required relationship " + field)
			}
		}

		// pass validation
		return nil
	}
}

// MatchingReferencesValidator compares the model with a related model and
// checks if certain references are shared.
//
// The target model is defined by passing its collection and the referencing
// field on the current model. The matcher is defined by passing pairs of
// database fields on the target and current model:
//
//		MatchingReferencesValidator("posts", "post_id", map[string]string{
// 			"user_id": "user_id",
// 		})
//
func MatchingReferencesValidator(collection, reference string, matcher map[string]string) Callback {
	return func(ctx *Context) error {
		// only run validator on Create and Update
		if ctx.Action != Create && ctx.Action != Update {
			return nil
		}

		// get main reference
		id := ctx.Model.MustGet(reference)

		// continue if reference is not set
		if oid, ok := id.(*bson.ObjectId); ok && oid == nil {
			return nil
		}

		// prepare query
		query := bson.M{
			"_id": id,
		}

		// add other references
		for targetField, modelField := range matcher {
			id := ctx.Model.MustGet(modelField)

			// abort if reference is missing
			if oid, ok := id.(*bson.ObjectId); ok && oid == nil {
				return errors.New("Missing ID")
			}

			query[targetField] = id
		}

		// query db
		n, err := ctx.Store.DB().C(collection).Find(query).Limit(1).Count()
		if err != nil {
			return Fatal(err)
		}

		// return error if document is missing
		if n == 0 {
			return errors.New("References do not match")
		}

		return nil
	}
}

// UniqueAttributeValidator ensures that the specified attribute of the
// controllers Model will remain unique among the specified filters.
//
// The unique attribute is defines as the first argument. Filters are defined
// by passing a list of database fields:
//
//		UniqueAttributeValidator("name", []string{ "user_id" })
//
func UniqueAttributeValidator(uniqueAttribute string, filters []string) Callback {
	return func(ctx *Context) error {
		// only run validator on Create and Update
		if ctx.Action != Create && ctx.Action != Update {
			return nil
		}

		// prepare query
		query := bson.M{
			uniqueAttribute: ctx.Model.MustGet(uniqueAttribute),
		}

		// add filters
		for _, field := range filters {
			query[field] = ctx.Model.MustGet(field)
		}

		// count
		n, err := ctx.Store.C(ctx.Model).Find(query).Limit(1).Count()
		if err != nil {
			return Fatal(err)
		} else if n != 0 {
			return fmt.Errorf("Attribute %s is not unique", uniqueAttribute)
		}

		return nil
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"os"
	"strings"
)

// -----------------------
// --- Functions
// -----------------------

func logFail(format string, v ...interface{}) {
	errorMsg := fmt.Sprintf(format, v...)
	fmt.Printf("\x1b[31;1m%s\x1b[0m\n", errorMsg)
	os.Exit(1)
}

func logWarn(format string, v ...interface{}) {
	errorMsg := fmt.Sprintf(format, v...)
	fmt.Printf("\x1b[33;1m%s\x1b[0m\n", errorMsg)
}

func logInfo(format string, v ...interface{}) {
	fmt.Println()
	errorMsg := fmt.Sprintf(format, v...)
	fmt.Printf("\x1b[34;1m%s\x1b[0m\n", errorMsg)
}

func logDetails(format string, v ...interface{}) {
	errorMsg := fmt.Sprintf(format, v...)
	fmt.Printf("  %s\n", errorMsg)
}

func logDone(format string, v ...interface{}) {
	errorMsg := fmt.Sprintf(format, v...)
	fmt.Printf("  \x1b[32;1m%s\x1b[0m\n", errorMsg)
}

func validateRequiredInput(key string) string {
	value := os.Getenv(key)
	if value == "" {
		logFail("missing required input: %s", key)
	}
	return value
}

func strip(str string) string {
	dirty := true
	strippedStr := str
	for dirty {
		hasWhiteSpacePrefix := false
		if strings.HasPrefix(strippedStr, " ") {
			hasWhiteSpacePrefix = true
			strippedStr = strings.TrimPrefix(strippedStr, " ")
		}

		hasWhiteSpaceSuffix := false
		if strings.HasSuffix(strippedStr, " ") {
			hasWhiteSpaceSuffix = true
			strippedStr = strings.TrimSuffix(strippedStr, " ")
		}

		if !hasWhiteSpacePrefix && !hasWhiteSpaceSuffix {
			dirty = false
		}
	}
	return strippedStr
}

func genericIsPathExists(pth string) (os.FileInfo, bool, error) {
	if pth == "" {
		return nil, false, errors.New("No path provided")
	}
	fileInf, err := os.Stat(pth)
	if err == nil {
		return fileInf, true, nil
	}
	if os.IsNotExist(err) {
		return nil, false, nil
	}
	return fileInf, false, err
}

func isPathExists(pth string) (bool, error) {
	_, isExists, err := genericIsPathExists(pth)
	return isExists, err
}

func createRequest(url string, fields, files map[string]string) (*http.Request, error) {
	var b bytes.Buffer
	w := multipart.NewWriter(&b)

	// Add fields
	for key, value := range fields {
		if err := w.WriteField(key, value); err != nil {
			return nil, err
		}
	}

	// Add files
	for key, file := range files {
		f, err := os.Open(file)
		if err != nil {
			return nil, err
		}
		fw, err := w.CreateFormFile(key, file)
		if err != nil {
			return nil, err
		}
		if _, err = io.Copy(fw, f); err != nil {
			return nil, err
		}
	}

	w.Close()

	req, err := http.NewRequest("POST", url, &b)
	if err != nil {
		return nil, err
	}

	req.Header.Set("Content-Type", w.FormDataContentType())

	return req, nil
}

// -----------------------
// --- Main
// -----------------------

func main() {
	//
	// Validate options
	logInfo("Configs:")
	logDetails("api_key: %s", os.Getenv("api_key"))
	logDetails("domain: %s", os.Getenv("domain"))
	logDetails("from_email: %s", os.Getenv("from_email"))
	logDetails("send_to: %s", os.Getenv("send_to"))
	logDetails("subject: %s", os.Getenv("subject"))
	logDetails("message: %s", os.Getenv("message"))
	logDetails("error_message: %s", os.Getenv("error_message"))
	logDetails("message_format: %s", os.Getenv("message_format"))
	logDetails("attachments: %s", os.Getenv("attachments"))

	apiKey := validateRequiredInput("api_key")
	domain := validateRequiredInput("domain")
	fromEmail := validateRequiredInput("from_email")
	sendTo := validateRequiredInput("send_to")
	subject := validateRequiredInput("subject")
	message := validateRequiredInput("message")
	messageFormat := validateRequiredInput("message_format")

	errorMessage := os.Getenv("error_message")
	attachmentList := os.Getenv("attachments")

	isBuildFailedMode := (os.Getenv("STEPLIB_BUILD_STATUS") != "0")
	if isBuildFailedMode && errorMessage != "" {
		message = errorMessage
	}

	attachments := []string{}
	if attachmentList != "" {
		split := strings.Split(attachmentList, ",")
		for _, item := range split {
			filePath := strip(item)
			if exist, err := isPathExists(filePath); err != nil {
				logFail("failed to check if path (%s) exist, error: %s", filePath, err)
			} else if !exist {
				logFail("file not exist at (%s)", filePath)
			}

			attachments = append(attachments, filePath)
		}
	}

	//
	// Create request
	logInfo("Performing request")

	requestURL := fmt.Sprintf("https://api.mailgun.net/v2/%s/messages", domain)

	fields := map[string]string{
		"from":    fromEmail,
		"to":      sendTo,
		"subject": subject,
	}

	switch messageFormat {
	case "html":
		fields["html"] = message
	case "text":
		fields["text"] = message
	default:
		logFail("invalid message_format: %s, available options [html, text]", messageFormat)
	}

	files := map[string]string{}
	if len(attachments) > 0 {
		for _, file := range attachments {
			files["attachment"] = file
		}
	}

	request, err := createRequest(requestURL, fields, files)
	if err != nil {
		logFail("Failed to create request, error: %#v", err)
	}
	request.SetBasicAuth("api", apiKey)

	client := http.Client{}
	response, requestErr := client.Do(request)
	if requestErr != nil {
		logFail("Performing request failed, error: %#v", requestErr)
	}

	defer response.Body.Close()
	contents, readErr := ioutil.ReadAll(response.Body)
	if readErr != nil {
		logWarn("Failed to read response body, error: %#v", readErr)
	}

	logInfo("response content: %s", contents)
	logDetails("status code: %d", response.StatusCode)
}
<commit_msg>Upload all attachments not just the last one<commit_after>package main

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"os"
	"strings"
)

// -----------------------
// --- Functions
// -----------------------

func logFail(format string, v ...interface{}) {
	errorMsg := fmt.Sprintf(format, v...)
	fmt.Printf("\x1b[31;1m%s\x1b[0m\n", errorMsg)
	os.Exit(1)
}

func logWarn(format string, v ...interface{}) {
	errorMsg := fmt.Sprintf(format, v...)
	fmt.Printf("\x1b[33;1m%s\x1b[0m\n", errorMsg)
}

func logInfo(format string, v ...interface{}) {
	fmt.Println()
	errorMsg := fmt.Sprintf(format, v...)
	fmt.Printf("\x1b[34;1m%s\x1b[0m\n", errorMsg)
}

func logDetails(format string, v ...interface{}) {
	errorMsg := fmt.Sprintf(format, v...)
	fmt.Printf("  %s\n", errorMsg)
}

func logDone(format string, v ...interface{}) {
	errorMsg := fmt.Sprintf(format, v...)
	fmt.Printf("  \x1b[32;1m%s\x1b[0m\n", errorMsg)
}

func validateRequiredInput(key string) string {
	value := os.Getenv(key)
	if value == "" {
		logFail("missing required input: %s", key)
	}
	return value
}

func strip(str string) string {
	dirty := true
	strippedStr := str
	for dirty {
		hasWhiteSpacePrefix := false
		if strings.HasPrefix(strippedStr, " ") {
			hasWhiteSpacePrefix = true
			strippedStr = strings.TrimPrefix(strippedStr, " ")
		}

		hasWhiteSpaceSuffix := false
		if strings.HasSuffix(strippedStr, " ") {
			hasWhiteSpaceSuffix = true
			strippedStr = strings.TrimSuffix(strippedStr, " ")
		}

		if !hasWhiteSpacePrefix && !hasWhiteSpaceSuffix {
			dirty = false
		}
	}
	return strippedStr
}

func genericIsPathExists(pth string) (os.FileInfo, bool, error) {
	if pth == "" {
		return nil, false, errors.New("No path provided")
	}
	fileInf, err := os.Stat(pth)
	if err == nil {
		return fileInf, true, nil
	}
	if os.IsNotExist(err) {
		return nil, false, nil
	}
	return fileInf, false, err
}

func isPathExists(pth string) (bool, error) {
	_, isExists, err := genericIsPathExists(pth)
	return isExists, err
}

func createRequest(url string, fields map[string]string, attachments []string) (*http.Request, error) {
	var b bytes.Buffer
	w := multipart.NewWriter(&b)

	// Add fields
	for key, value := range fields {
		if err := w.WriteField(key, value); err != nil {
			return nil, err
		}
	}

	// Add files
	for _, file := range attachments {
		f, err := os.Open(file)
		if err != nil {
			return nil, err
		}
		fw, err := w.CreateFormFile("attachment", file)
		if err != nil {
			return nil, err
		}
		if _, err = io.Copy(fw, f); err != nil {
			return nil, err
		}
	}

	w.Close()

	req, err := http.NewRequest("POST", url, &b)
	if err != nil {
		return nil, err
	}

	req.Header.Set("Content-Type", w.FormDataContentType())

	return req, nil
}

// -----------------------
// --- Main
// -----------------------

func main() {
	//
	// Validate options
	logInfo("Configs:")
	logDetails("api_key: %s", os.Getenv("api_key"))
	logDetails("domain: %s", os.Getenv("domain"))
	logDetails("from_email: %s", os.Getenv("from_email"))
	logDetails("send_to: %s", os.Getenv("send_to"))
	logDetails("subject: %s", os.Getenv("subject"))
	logDetails("message: %s", os.Getenv("message"))
	logDetails("error_message: %s", os.Getenv("error_message"))
	logDetails("message_format: %s", os.Getenv("message_format"))
	logDetails("attachments: %s", os.Getenv("attachments"))

	apiKey := validateRequiredInput("api_key")
	domain := validateRequiredInput("domain")
	fromEmail := validateRequiredInput("from_email")
	sendTo := validateRequiredInput("send_to")
	subject := validateRequiredInput("subject")
	message := validateRequiredInput("message")
	messageFormat := validateRequiredInput("message_format")

	errorMessage := os.Getenv("error_message")
	attachmentList := os.Getenv("attachments")

	isBuildFailedMode := (os.Getenv("STEPLIB_BUILD_STATUS") != "0")
	if isBuildFailedMode && errorMessage != "" {
		message = errorMessage
	}

	attachments := []string{}
	if attachmentList != "" {
		split := strings.Split(attachmentList, ",")
		for _, item := range split {
			filePath := strip(item)
			if exist, err := isPathExists(filePath); err != nil {
				logFail("failed to check if path (%s) exist, error: %s", filePath, err)
			} else if !exist {
				logFail("file not exist at (%s)", filePath)
			}

			attachments = append(attachments, filePath)
		}
	}

	//
	// Create request
	logInfo("Performing request")

	requestURL := fmt.Sprintf("https://api.mailgun.net/v2/%s/messages", domain)

	fields := map[string]string{
		"from":    fromEmail,
		"to":      sendTo,
		"subject": subject,
	}

	switch messageFormat {
	case "html":
		fields["html"] = message
	case "text":
		fields["text"] = message
	default:
		logFail("invalid message_format: %s, available options [html, text]", messageFormat)
	}

	request, err := createRequest(requestURL, fields, attachments)
	if err != nil {
		logFail("Failed to create request, error: %#v", err)
	}
	request.SetBasicAuth("api", apiKey)

	client := http.Client{}
	response, requestErr := client.Do(request)
	if requestErr != nil {
		logFail("Performing request failed, error: %#v", requestErr)
	}

	defer response.Body.Close()
	contents, readErr := ioutil.ReadAll(response.Body)
	if readErr != nil {
		logWarn("Failed to read response body, error: %#v", readErr)
	}

	logInfo("response content: %s", contents)
	logDetails("status code: %d", response.StatusCode)
}
<|endoftext|>


<commit_before>package cas

import (
    "os"
    r "github.com/dancannon/gorethink"
)

// CAS server configuration object
type CASServerConfig struct {
    Host string
    Port string
    DBHost string
    DBName string
    TemplatesDirectory string
    CompanyName string
    RDBSession *r.Session
}

func (c *CASServerConfig) GetAddr() string {
    return c.Host + ":" + c.Port
}

func (c *CASServerConfig) OverrideWithEnvVariables() {
    // Environment overrides
    if v := os.Getenv("CASGO_HOST"); len(v) > 0 {
        c.Host = os.Getenv("CASGO_HOST")
    }
    if v := os.Getenv("CASGO_PORT"); len(v) > 0 {
        c.Port = os.Getenv("CASGO_PORT")
    }
    if v := os.Getenv("CASGO_DBHOST"); len(v) > 0 {
        c.Port = os.Getenv("CASGO_DBHOST")
    }
    if v := os.Getenv("CASGO_DBNAME"); len(v) > 0 {
        c.Port = os.Getenv("CASGO_DBNAME")
    }
    if v := os.Getenv("CASGO_TEMPLATES"); len(v) > 0 {
        c.TemplatesDirectory = os.Getenv("CASGO_TEMPLATES")
    }
    if v := os.Getenv("CASGO_COMPNAME"); len(v) > 0 {
        c.CompanyName = os.Getenv("CASGO_COMPNAME")
    }
}
<commit_msg>Fix broken ENV overrides<commit_after>package cas

import (
    "os"
    r "github.com/dancannon/gorethink"
)

// CAS server configuration object
type CASServerConfig struct {
    Host string
    Port string
    DBHost string
    DBName string
    CookieSecret string
    TemplatesDirectory string
    CompanyName string
    RDBSession *r.Session
}

func (c *CASServerConfig) GetAddr() string {
    return c.Host + ":" + c.Port
}

func (c *CASServerConfig) OverrideWithEnvVariables() {
    // Environment overrides
    if v := os.Getenv("CASGO_HOST"); len(v) > 0 {
        c.Host = os.Getenv("CASGO_HOST")
    }
    if v := os.Getenv("CASGO_PORT"); len(v) > 0 {
        c.Port = os.Getenv("CASGO_PORT")
    }
    if v := os.Getenv("CASGO_DBHOST"); len(v) > 0 {
        c.DBHost = os.Getenv("CASGO_DBHOST")
    }
    if v := os.Getenv("CASGO_DBNAME"); len(v) > 0 {
        c.DBName = os.Getenv("CASGO_DBNAME")
    }
    if v := os.Getenv("CASGO_SECRET"); len(v) > 0 {
        c.CookieSecret = os.Getenv("CASGO_SECRET")
    }
    if v := os.Getenv("CASGO_TEMPLATES"); len(v) > 0 {
        c.TemplatesDirectory = os.Getenv("CASGO_TEMPLATES")
    }
    if v := os.Getenv("CASGO_COMPNAME"); len(v) > 0 {
        c.CompanyName = os.Getenv("CASGO_COMPNAME")
    }
}
<|endoftext|>


<commit_before>package certstream

import (
	"time"
	"github.com/gorilla/websocket"
	"github.com/jmoiron/jsonq"
)

type CertstreamError struct {
	errorCode string
	errorString string
	error *error
}

func CertStreamEventStream(skipHeartbeats bool) (chan jsonq.JsonQuery, chan CertstreamError) {
	outputStream := make(chan jsonq.JsonQuery)
	errStream := make(chan CertstreamError)

	go func() {
		for {
			c, _, err := websocket.DefaultDialer.Dial("wss://certstream.calidog.io", nil)

			if err != nil {
				errStream <- CertstreamError{
					"CONNECTION_ERROR",
					"Error connecting to certstream! Sleeping a few seconds and reconnecting... ",
					&err,
				}
				time.Sleep(5 * time.Second)
				continue
			}

			defer c.Close()
			defer close(outputStream)

			for {
				var v interface{}
				err = c.ReadJSON(&v)
				if err != nil {
					errStream <- CertstreamError{
						"JSON_DECODE_ERROR",
						"Error decoding json frame!",
						&err,
					}
				}

				jq := jsonq.NewQuery(v)

				res, err := jq.String("message_type")
				if err != nil {
					errStream <- CertstreamError{
						"JQ_ERROR",
						"Error creating jq object!",
						&err,
					}
				}

				if (skipHeartbeats && res == "heartbeat"){
					continue
				}

				outputStream <- *jq
			}
		}
	}()

	return outputStream, errStream
}

<commit_msg>Move to pkg/errors to wrap the errors instead of sending a struct<commit_after>package certstream

import (
	"time"
	"github.com/gorilla/websocket"
	"github.com/jmoiron/jsonq"
	"github.com/pkg/errors"
)

func CertStreamEventStream(skipHeartbeats bool) (chan jsonq.JsonQuery, chan error) {
	outputStream := make(chan jsonq.JsonQuery)
	errStream := make(chan error)

	go func() {
		for {
			c, _, err := websocket.DefaultDialer.Dial("wss://certstream.calidog.io", nil)

			if err != nil {
				errStream <- errors.Wrap(err, "Error connecting to certstream! Sleeping a few seconds and reconnecting... ")
				time.Sleep(5 * time.Second)
				continue
			}

			defer c.Close()
			defer close(outputStream)

			for {
				var v interface{}
				err = c.ReadJSON(&v)
				if err != nil {
					errStream <- errors.Wrap(err, "Error decoding json frame!")
				}

				jq := jsonq.NewQuery(v)

				res, err := jq.String("message_type")
				if err != nil {
					errStream <- errors.Wrap(err, "Error creating jq object!")
				}

				if (skipHeartbeats && res == "heartbeat"){
					continue
				}

				outputStream <- *jq
			}
		}
	}()

	return outputStream, errStream
}

<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/BurntSushi/toml"
	"log"
	"sync"
	"time"
)

//Struct representing the config.toml
type tomlConfig struct {
	Miners map[string]miner //Key is the miner name.
}

//Struct for config file type [miners.<foo>]
type miner struct {
	IP string
}

type MinerInformation struct {
	Mu       sync.Mutex //So we dont read and write to it at the same time.
	Version  string     //Version responce
	Hashrate string     //Hashrate response for the miner
}

func main() {

	//Start by reading the config file
	var config tomlConfig
	_, err := toml.DecodeFile("config.toml", &config)
	//Check for errors
	if err != nil {
		fmt.Println(err)
		return
	}

	minerStruct := MinerInformation{}

	//No errors from loading the config file to start
	//fetching information from each miner
	for minerName, miner := range config.Miners {
		fmt.Printf("Server: %s(%s)\n", minerName, miner.IP)
		//Start one new gorutine for each miner
		go rpcClient(minerName, miner.IP, 10, &minerStruct)
	}
	for {
		//Lock it
		minerStruct.Mu.Lock()
		//Read it
		log.Println(minerStruct.Hashrate)
		//Unlock it
		minerStruct.Mu.Unlock()
		//Sleep for some time
		time.Sleep(2 * time.Second)
	}
}
<commit_msg>More work done how the responces from the miners get stored and printed<commit_after>package main

import (
	"fmt"
	"github.com/BurntSushi/toml"
	"log"
	"sync"
	"time"
)

//Struct representing the config.toml
type tomlConfig struct {
	Miners map[string]miner //Key is the miner name.
}

//Struct for config file type [miners.<foo>]
type miner struct {
	IP string
}

type MinerInformation struct {
	Mu       sync.Mutex //So we dont read and write to it at the same time.
	Name     string     //The miners name
	Version  string     //Version responce
	Hashrate string     //Hashrate response for the miner
}

func main() {

	//Start by reading the config file
	var config tomlConfig
	_, err := toml.DecodeFile("config.toml", &config)
	//Check for errors
	if err != nil {
		fmt.Println(err)
		return
	}

	miners := make([]MinerInformation, len(config.Miners))

	//Start to grab information from every miner
	for minerName, miner := range config.Miners {
		log.Printf("Server: %s(%s)\n", minerName, miner.IP)
		//Create a new miner struct and add the name
		minerStructTemp := MinerInformation{}
		minerStructTemp.Name = minerName

		//Add save it
		miners = append(miners, minerStructTemp)

		//Start one new gorutine for each miner
		go rpcClient(minerName, miner.IP, 10, &minerStructTemp)
	}

	//Loop for ever
	for {
		//Iterate over each miner reponce and print it
		for _, minerInfo := range miners {
			//Lock it
			minerInfo.Mu.Lock()
			//Read it
			log.Println(minerInfo.Name)
			log.Println(minerInfo.Hashrate)
			log.Println("")
			//Unlock it
			minerInfo.Mu.Unlock()
		}
		//Sleep for some time
		time.Sleep(2 * time.Second)
	}
}
<|endoftext|>


<commit_before><commit_msg>[cmd/web] 修正无法指定  buildDate 的错误<commit_after><|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"strings"
	"text/tabwriter"

	"github.com/bradfitz/slice"
)

// Character is the type representing a role playing character
type Character struct {
	Name            string
	Backgrounds     map[string][]Background
	Aptitudes       []Aptitude
	Characteristics map[*Characteristic]int
	Skills          map[*Skill]int
	Talents         map[*Talent]int
	Gauges          map[*Gauge]int
	Rules           []Rule
	Experience      int
	Spent           int
}

// NewCharacter creates a new character given a sheet
func NewCharacter(u Universe, s Sheet) (*Character, error) {

	// Create the character.
	c := &Character{}

	// Alias Header.
	h := s.Header

	// Retrieve character's name.
	c.Name = h.Name

	// Apply the initial characteristics from the sheet.
	c.Characteristics = make(map[*Characteristic]int)
	for _, characteristic := range s.Characteristics {

		// Identify name and value.
		name, value, _, err := IdentifyCharacteristic(characteristic)
		if err != nil {
			return nil, err
		}

		// Retrieve characteristic from universe given it's name.
		char, found := u.FindCharacteristic(name)
		if !found {
			return nil, NewError(UndefinedCharacteristic, characteristic.Line)
		}

		// Check the characteristic is not set twice
		_, found = c.Characteristics[&char]
		if found {
			return nil, NewError(DuplicateCharacteristic, characteristic.Line)
		}

		// Associate the characteristic and it' value to the characteristics map
		c.Characteristics[&char] = value
	}

	// Check all characteristics are defined for the character
checkCharacteristics:
	for _, u := range u.Characteristics {
		for c := range c.Characteristics {
			if c.Name == u.Name {
				continue checkCharacteristics
			}
		}
		return nil, NewError(MissingCharacteristic, u.Name)
	}

	// Make the character's gauges, skills and talents maps
	c.Skills = make(map[*Skill]int)
	c.Talents = make(map[*Talent]int)
	c.Gauges = make(map[*Gauge]int)

	// Apply each Meta.
	c.Backgrounds = make(map[string][]Background)
	for typ, metas := range h.Metas {

		if len(metas) == 0 {
			panic(fmt.Sprintf("empty metas for type %s", typ))
		}
		line := metas[0].Line

		bagrounds, found := u.Backgrounds[typ]

		// Check the background type exists in universe.
		if !found {
			return nil, NewError(UndefinedBackgroundType, line, typ)
		}

		c.Backgrounds[typ] = []Background{}

	metasLoop:
		for _, meta := range metas {

			// Search the background corresponding to the provided meta.
			for _, b := range bagrounds {
				if meta.Label != b.Name {
					continue
				}

				// Apply the background.
				c.Backgrounds[typ] = append(c.Backgrounds[typ], b)
				err := c.ApplyBackground(b, u)
				if err != nil {
					return nil, err
				}

				continue metasLoop
			}
			return nil, NewError(UndefinedBackgroundValue, line, meta.Label, typ)
		}
	}

	// Apply the sessions.
	for _, s := range s.Sessions {

		// Add experience value.
		if s.Reward != nil {
			c.Experience += *s.Reward
		}

		// For each upgrade.
		for _, up := range s.Upgrades {

			// Apply the upgrade.
			err := c.ApplyUpgrade(up, u)
			if err != nil {
				return nil, err
			}
		}
	}

	return c, nil
}

// ApplyBackground changes the character's trait according to the history values
func (c *Character) ApplyBackground(h Background, u Universe) error {

	// For each upgrade associated to the history, apply each option.
	for _, upgrades := range h.Upgrades {
		for _, option := range upgrades {
			err := c.ApplyUpgrade(option, u)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

// ApplyUpgrade changes the character's trait according to the given upgrade
func (c *Character) ApplyUpgrade(up Upgrade, un Universe) error {

	// Defer payment of upgrade
	var payed bool
	var coster Coster

	// Pay the upgrade.
	switch {

	// Upgrade is free.
	case up.Mark == "-":
		if up.Cost != nil {
			return NewError(MismatchMarkCost, up.Line)
		}
		payed = true

	// Cost is hard defined.
	case up.Cost != nil:
		c.Spent += *up.Cost
		payed = true
	}

	// Identify characteristic.
	name, value, sign, err := IdentifyCharacteristic(up)
	if err == nil {

		// Check the characteristic exists.
		characteristic, found := un.FindCharacteristic(name)
		if !found {
			return NewError(UndefinedCharacteristic, up.Line)
		}

		// Apply the modification to the character's characteristic.
		// The characteristic must have this characteristic
		for char, v := range c.Characteristics {
			if char.Name == characteristic.Name {

				// Increment the characteristic Tier.
				if up.Mark == "*" {
					char.Tier++
				}

				// Apply the characteristic.
				c.Characteristics[char] = ApplyCharacteristicUpgrade(v, sign, value)
				coster = char

				// Pay for it
				if !payed && coster != nil && err == nil {
					var cost int
					// Transmit the error value to the parent func
					cost, err = coster.Cost(un.Costs, c.Aptitudes)
					if err != nil {
						return err
					}
					c.Spent += cost
				}
				return nil
			}
		}
		panic(fmt.Sprintf("unidefined characteristic %s for character", characteristic.Name))
	}

	// Identify aptitude.
	aptitude, found := un.FindAptitude(up.Name)
	if found {
		c.Aptitudes = append(c.Aptitudes, aptitude)
		return nil
	}

	// Sort aptitudes by name and remove duplicates.
	slice.Sort(c.Aptitudes, func(i, j int) bool {
		return c.Aptitudes[i] < c.Aptitudes[j]
	})
	var aptitudes []Aptitude
	for _, a := range c.Aptitudes {
		if len(aptitudes) != 0 && aptitudes[len(aptitudes)-1] == a {
			continue
		}
		aptitudes = append(aptitudes, a)
	}
	c.Aptitudes = aptitudes

	// Identify skill or talent.
	name, speciality, err := up.Split()
	if err != nil {
		return err
	}

	// Skill identified.
	skill, isSkill := un.FindSkill(name)

	if isSkill {

		// The skill has a speciality
		if len(speciality) != 0 {
			skill.Name = fmt.Sprintf("%s: %s", name, speciality)
		}

		// Look for the skill with the given name in the
		// character's skill, and apply the modification
		for s := range c.Skills {
			if s.Name == skill.Name {

				// Increment the skill tier
				if up.Mark == "*" {
					s.Tier++
				}

				// Change the value of the skill of index s
				c.Skills[s] += 10
				coster = s

				// Pay for it
				if !payed && coster != nil && err == nil {
					var cost int
					// Transmit the error value to the parent func
					cost, err = coster.Cost(un.Costs, c.Aptitudes)
					if err != nil {
						return err
					}
					c.Spent += cost
				}
				return nil
			}
		}

		// Increment the skill tier
		if up.Mark == "*" {
			skill.Tier++
		}

		// Create the skill of index *skill
		c.Skills[&skill] = 0
		coster = &skill

		// Pay for it
		if !payed && coster != nil && err == nil {
			var cost int
			// Transmit the error value to the parent func
			cost, err = coster.Cost(un.Costs, c.Aptitudes)
			if err != nil {
				return err
			}
			c.Spent += cost
		}
		return nil
	}

	// Talent identified.
	talent, isTalent := un.FindTalent(name)
	if isTalent {

		// The talent has a speciality
		if len(speciality) != 0 {
			talent.Name = fmt.Sprintf("%s: %s", name, speciality)
		}

		// Look for the talent with the given name in
		// the character's talents, and apply the modification
		for s := range c.Talents {
			if s.Name == talent.Name {

				// Change the value of the talent of index s
				c.Talents[s]++
				coster = s

				// Pay for it
				if !payed && coster != nil && err == nil {
					var cost int
					// Transmit the error value to the parent func
					cost, err = coster.Cost(un.Costs, c.Aptitudes)
					if err != nil {
						return err
					}
					c.Spent += cost
				}
				return nil
			}
		}

		// Create the talent of index *talent
		c.Talents[&talent] = 1
		coster = &talent

		// Pay for it
		if !payed && coster != nil && err == nil {
			var cost int
			// Transmit the error value to the parent func
			cost, err = coster.Cost(un.Costs, c.Aptitudes)
			if err != nil {
				return err
			}
			c.Spent += cost
		}
		return nil
	}

	// It's a special rule
	rule := Rule{
		Name:        name,
		Description: speciality,
	}
	c.Rules = append(c.Rules, rule)

	return nil
}

// Print the character sheet on the screen
func (c Character) Print() {
	// Print the name
	fmt.Printf("%s\t%s\n", theme.Title("Name"), c.Name)

	// Print the backgrounds
	backgrounds := []Background{}
	for _, list := range c.Backgrounds {
		backgrounds = append(backgrounds, list...)
	}

	slice.Sort(backgrounds, func(i, j int) bool {
		if backgrounds[i].Type != backgrounds[j].Type {
			return backgrounds[i].Type < backgrounds[j].Type
		}

		return backgrounds[i].Name < backgrounds[j].Name
	})

	for _, background := range backgrounds {
		fmt.Printf("%s\t%s\n", theme.Title(strings.Title(background.Type)), strings.Title(background.Name))
	}

	// Print the experience
	fmt.Printf("\n%s\t%d/%d\n", theme.Title("Experience"), c.Spent, c.Experience)

	// Print the characteristics
	fmt.Printf("\n%s\n", theme.Title("Characteristics"))

	characteristics := []*Characteristic{}
	for characteristic, _ := range c.Characteristics {
		characteristics = append(characteristics, characteristic)
	}

	slice.Sort(characteristics, func(i, j int) bool {
		return characteristics[i].Name < characteristics[j].Name
	})

	for _, characteristic := range characteristics {
		fmt.Printf("%s\t%s\n", characteristic.Name, theme.Value(c.Characteristics[characteristic]))
	}

	// Print the talents
	fmt.Printf("\n%s\n", theme.Title("Talents"))

	talents := []*Talent{}
	for talent, _ := range c.Talents {
		talents = append(talents, talent)
	}

	slice.Sort(talents, func(i, j int) bool {
		return talents[i].Name < talents[j].Name
	})

	for _, talent := range talents {
		if c.Talents[talent] != 1 {
			fmt.Printf("%s (%s)\n", strings.Title(talent.Name), theme.Value(c.Talents[talent]))
		} else {
			fmt.Printf("%s\n", strings.Title(talent.Name))
		}
	}

	// Print the skills using a tabwriter
	fmt.Printf("\n%s\n", theme.Title("Skills"))

	skills := []*Skill{}
	for skill, _ := range c.Skills {
		skills = append(skills, skill)
	}

	slice.Sort(skills, func(i, j int) bool {
		return skills[i].Name < skills[j].Name
	})

	w := tabwriter.NewWriter(os.Stdout, 10, 1, 2, ' ', 0)
	for _, skill := range skills {
		fmt.Fprintf(w, "%s\t%s\n", strings.Title(skill.Name), theme.Value(c.Skills[skill]))
	}
	w.Flush()

	// Print the special rules
	fmt.Printf("\n%s\n", theme.Title("Rules"))

	rules := c.Rules
	slice.Sort(rules, func(i, j int) bool {
		return rules[i].Name < rules[j].Name
	})

	for _, rule := range rules {
		fmt.Printf("%s\t%s\n", strings.Title(rule.Name), rule.Description)
	}
}
<commit_msg>Display multiple backgrounds on the same line<commit_after>package main

import (
	"fmt"
	"os"
	"strings"
	"text/tabwriter"

	"github.com/bradfitz/slice"
)

// Character is the type representing a role playing character
type Character struct {
	Name            string
	Backgrounds     map[string][]Background
	Aptitudes       []Aptitude
	Characteristics map[*Characteristic]int
	Skills          map[*Skill]int
	Talents         map[*Talent]int
	Gauges          map[*Gauge]int
	Rules           []Rule
	Experience      int
	Spent           int
}

// NewCharacter creates a new character given a sheet
func NewCharacter(u Universe, s Sheet) (*Character, error) {

	// Create the character.
	c := &Character{}

	// Alias Header.
	h := s.Header

	// Retrieve character's name.
	c.Name = h.Name

	// Apply the initial characteristics from the sheet.
	c.Characteristics = make(map[*Characteristic]int)
	for _, characteristic := range s.Characteristics {

		// Identify name and value.
		name, value, _, err := IdentifyCharacteristic(characteristic)
		if err != nil {
			return nil, err
		}

		// Retrieve characteristic from universe given it's name.
		char, found := u.FindCharacteristic(name)
		if !found {
			return nil, NewError(UndefinedCharacteristic, characteristic.Line)
		}

		// Check the characteristic is not set twice
		_, found = c.Characteristics[&char]
		if found {
			return nil, NewError(DuplicateCharacteristic, characteristic.Line)
		}

		// Associate the characteristic and it' value to the characteristics map
		c.Characteristics[&char] = value
	}

	// Check all characteristics are defined for the character
checkCharacteristics:
	for _, u := range u.Characteristics {
		for c := range c.Characteristics {
			if c.Name == u.Name {
				continue checkCharacteristics
			}
		}
		return nil, NewError(MissingCharacteristic, u.Name)
	}

	// Make the character's gauges, skills and talents maps
	c.Skills = make(map[*Skill]int)
	c.Talents = make(map[*Talent]int)
	c.Gauges = make(map[*Gauge]int)

	// Apply each Meta.
	c.Backgrounds = make(map[string][]Background)
	for typ, metas := range h.Metas {

		if len(metas) == 0 {
			panic(fmt.Sprintf("empty metas for type %s", typ))
		}
		line := metas[0].Line

		bagrounds, found := u.Backgrounds[typ]

		// Check the background type exists in universe.
		if !found {
			return nil, NewError(UndefinedBackgroundType, line, typ)
		}

		c.Backgrounds[typ] = []Background{}

	metasLoop:
		for _, meta := range metas {

			// Search the background corresponding to the provided meta.
			for _, b := range bagrounds {
				if meta.Label != b.Name {
					continue
				}

				// Apply the background.
				c.Backgrounds[typ] = append(c.Backgrounds[typ], b)
				err := c.ApplyBackground(b, u)
				if err != nil {
					return nil, err
				}

				continue metasLoop
			}
			return nil, NewError(UndefinedBackgroundValue, line, meta.Label, typ)
		}
	}

	// Apply the sessions.
	for _, s := range s.Sessions {

		// Add experience value.
		if s.Reward != nil {
			c.Experience += *s.Reward
		}

		// For each upgrade.
		for _, up := range s.Upgrades {

			// Apply the upgrade.
			err := c.ApplyUpgrade(up, u)
			if err != nil {
				return nil, err
			}
		}
	}

	return c, nil
}

// ApplyBackground changes the character's trait according to the history values
func (c *Character) ApplyBackground(h Background, u Universe) error {

	// For each upgrade associated to the history, apply each option.
	for _, upgrades := range h.Upgrades {
		for _, option := range upgrades {
			err := c.ApplyUpgrade(option, u)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

// ApplyUpgrade changes the character's trait according to the given upgrade
func (c *Character) ApplyUpgrade(up Upgrade, un Universe) error {

	// Defer payment of upgrade
	var payed bool
	var coster Coster

	// Pay the upgrade.
	switch {

	// Upgrade is free.
	case up.Mark == "-":
		if up.Cost != nil {
			return NewError(MismatchMarkCost, up.Line)
		}
		payed = true

	// Cost is hard defined.
	case up.Cost != nil:
		c.Spent += *up.Cost
		payed = true
	}

	// Identify characteristic.
	name, value, sign, err := IdentifyCharacteristic(up)
	if err == nil {

		// Check the characteristic exists.
		characteristic, found := un.FindCharacteristic(name)
		if !found {
			return NewError(UndefinedCharacteristic, up.Line)
		}

		// Apply the modification to the character's characteristic.
		// The characteristic must have this characteristic
		for char, v := range c.Characteristics {
			if char.Name == characteristic.Name {

				// Increment the characteristic Tier.
				if up.Mark == "*" {
					char.Tier++
				}

				// Apply the characteristic.
				c.Characteristics[char] = ApplyCharacteristicUpgrade(v, sign, value)
				coster = char

				// Pay for it
				if !payed && coster != nil && err == nil {
					var cost int
					// Transmit the error value to the parent func
					cost, err = coster.Cost(un.Costs, c.Aptitudes)
					if err != nil {
						return err
					}
					c.Spent += cost
				}
				return nil
			}
		}
		panic(fmt.Sprintf("unidefined characteristic %s for character", characteristic.Name))
	}

	// Identify aptitude.
	aptitude, found := un.FindAptitude(up.Name)
	if found {
		c.Aptitudes = append(c.Aptitudes, aptitude)
		return nil
	}

	// Sort aptitudes by name and remove duplicates.
	slice.Sort(c.Aptitudes, func(i, j int) bool {
		return c.Aptitudes[i] < c.Aptitudes[j]
	})
	var aptitudes []Aptitude
	for _, a := range c.Aptitudes {
		if len(aptitudes) != 0 && aptitudes[len(aptitudes)-1] == a {
			continue
		}
		aptitudes = append(aptitudes, a)
	}
	c.Aptitudes = aptitudes

	// Identify skill or talent.
	name, speciality, err := up.Split()
	if err != nil {
		return err
	}

	// Skill identified.
	skill, isSkill := un.FindSkill(name)

	if isSkill {

		// The skill has a speciality
		if len(speciality) != 0 {
			skill.Name = fmt.Sprintf("%s: %s", name, speciality)
		}

		// Look for the skill with the given name in the
		// character's skill, and apply the modification
		for s := range c.Skills {
			if s.Name == skill.Name {

				// Increment the skill tier
				if up.Mark == "*" {
					s.Tier++
				}

				// Change the value of the skill of index s
				c.Skills[s] += 10
				coster = s

				// Pay for it
				if !payed && coster != nil && err == nil {
					var cost int
					// Transmit the error value to the parent func
					cost, err = coster.Cost(un.Costs, c.Aptitudes)
					if err != nil {
						return err
					}
					c.Spent += cost
				}
				return nil
			}
		}

		// Increment the skill tier
		if up.Mark == "*" {
			skill.Tier++
		}

		// Create the skill of index *skill
		c.Skills[&skill] = 0
		coster = &skill

		// Pay for it
		if !payed && coster != nil && err == nil {
			var cost int
			// Transmit the error value to the parent func
			cost, err = coster.Cost(un.Costs, c.Aptitudes)
			if err != nil {
				return err
			}
			c.Spent += cost
		}
		return nil
	}

	// Talent identified.
	talent, isTalent := un.FindTalent(name)
	if isTalent {

		// The talent has a speciality
		if len(speciality) != 0 {
			talent.Name = fmt.Sprintf("%s: %s", name, speciality)
		}

		// Look for the talent with the given name in
		// the character's talents, and apply the modification
		for s := range c.Talents {
			if s.Name == talent.Name {

				// Change the value of the talent of index s
				c.Talents[s]++
				coster = s

				// Pay for it
				if !payed && coster != nil && err == nil {
					var cost int
					// Transmit the error value to the parent func
					cost, err = coster.Cost(un.Costs, c.Aptitudes)
					if err != nil {
						return err
					}
					c.Spent += cost
				}
				return nil
			}
		}

		// Create the talent of index *talent
		c.Talents[&talent] = 1
		coster = &talent

		// Pay for it
		if !payed && coster != nil && err == nil {
			var cost int
			// Transmit the error value to the parent func
			cost, err = coster.Cost(un.Costs, c.Aptitudes)
			if err != nil {
				return err
			}
			c.Spent += cost
		}
		return nil
	}

	// It's a special rule
	rule := Rule{
		Name:        name,
		Description: speciality,
	}
	c.Rules = append(c.Rules, rule)

	return nil
}

// Print the character sheet on the screen
func (c Character) Print() {
	// Print the name
	fmt.Printf("%s\t%s\n", theme.Title("Name"), c.Name)

	// Print the backgrounds
	types := []string{}
	for typ, _ := range c.Backgrounds {
		types = append(types, typ)
	}

	slice.Sort(types, func(i, j int) bool {
		return types[i] < types[j]
	})

	for _, typ := range types {
		backgrounds := []string{}

		for _, background := range c.Backgrounds[typ] {
			backgrounds = append(backgrounds, background.Name)
		}

		slice.Sort(backgrounds, func(i, j int) bool {
			return backgrounds[i] < backgrounds[j]
		})

		fmt.Printf("%s\t%s\n", theme.Title(strings.Title(typ)), strings.Title(strings.Join(backgrounds, ", ")))
	}

	// Print the experience
	fmt.Printf("\n%s\t%d/%d\n", theme.Title("Experience"), c.Spent, c.Experience)

	// Print the characteristics
	fmt.Printf("\n%s\n", theme.Title("Characteristics"))

	characteristics := []*Characteristic{}
	for characteristic, _ := range c.Characteristics {
		characteristics = append(characteristics, characteristic)
	}

	slice.Sort(characteristics, func(i, j int) bool {
		return characteristics[i].Name < characteristics[j].Name
	})

	for _, characteristic := range characteristics {
		fmt.Printf("%s\t%s\n", characteristic.Name, theme.Value(c.Characteristics[characteristic]))
	}

	// Print the talents
	fmt.Printf("\n%s\n", theme.Title("Talents"))

	talents := []*Talent{}
	for talent, _ := range c.Talents {
		talents = append(talents, talent)
	}

	slice.Sort(talents, func(i, j int) bool {
		return talents[i].Name < talents[j].Name
	})

	for _, talent := range talents {
		if c.Talents[talent] != 1 {
			fmt.Printf("%s (%s)\n", strings.Title(talent.Name), theme.Value(c.Talents[talent]))
		} else {
			fmt.Printf("%s\n", strings.Title(talent.Name))
		}
	}

	// Print the skills using a tabwriter
	fmt.Printf("\n%s\n", theme.Title("Skills"))

	skills := []*Skill{}
	for skill, _ := range c.Skills {
		skills = append(skills, skill)
	}

	slice.Sort(skills, func(i, j int) bool {
		return skills[i].Name < skills[j].Name
	})

	w := tabwriter.NewWriter(os.Stdout, 10, 1, 2, ' ', 0)
	for _, skill := range skills {
		fmt.Fprintf(w, "%s\t%s\n", strings.Title(skill.Name), theme.Value(c.Skills[skill]))
	}
	w.Flush()

	// Print the special rules
	fmt.Printf("\n%s\n", theme.Title("Rules"))

	rules := c.Rules
	slice.Sort(rules, func(i, j int) bool {
		return rules[i].Name < rules[j].Name
	})

	for _, rule := range rules {
		fmt.Printf("%s\t%s\n", strings.Title(rule.Name), rule.Description)
	}
}
<|endoftext|>


<commit_before>package main

import (
	cachePkg "github.com/dimfeld/simpleblog/cache"
	"github.com/julienschmidt/httprouter"

	//	"io/ioutil"
	"net/http"
)

type Article struct {
	Title   string
	Tags    []string
	Content []byte
}

type GlobalData struct {
	// General cache
	cache cachePkg.Cache
}

type simpleBlogHandler func(*GlobalData, http.ResponseWriter, *http.Request, map[string]string)

func handlerWrapper(handler simpleBlogHandler, globalData *GlobalData) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, urlParams map[string]string) {
		handler(globalData, w, r, urlParams)
	}
}

func main() {
	// TODO Load configuration

	diskCache := cachePkg.NewDiskCache("cache")
	memCache := cachePkg.NewMemoryCache(64*1024*1024, diskCache)

	globalData := &GlobalData{cache: memCache}

	router := httprouter.New()
	router.GET("/", handlerWrapper(indexHandler, globalData))
	router.GET("/:year/:month", handlerWrapper(archiveHandler, globalData))
	router.GET("/:year/:month/:post", handlerWrapper(postHandler, globalData))
	// No tags yet.
	//router.GET("/tag/:tag", handlerWrapper(tagHandler, globalData))
	// No pagination yet.
	//router.GET("/tag/:tag/:page", handlerWrapper(tagHandler, globalData))
	router.GET("/images/*file", handlerWrapper(simpleHandler, globalData))
	router.GET("/assets/*file", handlerWrapper(staticCompressHandler, globalData))
	router.GET("/favicon.ico", handlerWrapper(simpleHandler, globalData))

	http.ListenAndServe(":8080", router)
}
<commit_msg>Allow compression of favicon.ico<commit_after>package main

import (
	cachePkg "github.com/dimfeld/simpleblog/cache"
	"github.com/julienschmidt/httprouter"

	//	"io/ioutil"
	"net/http"
)

type Article struct {
	Title   string
	Tags    []string
	Content []byte
}

type GlobalData struct {
	// General cache
	cache cachePkg.Cache
}

type simpleBlogHandler func(*GlobalData, http.ResponseWriter, *http.Request, map[string]string)

func handlerWrapper(handler simpleBlogHandler, globalData *GlobalData) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, urlParams map[string]string) {
		handler(globalData, w, r, urlParams)
	}
}

func main() {
	// TODO Load configuration

	diskCache := cachePkg.NewDiskCache("cache")
	memCache := cachePkg.NewMemoryCache(64*1024*1024, diskCache)

	globalData := &GlobalData{cache: memCache}

	router := httprouter.New()
	router.GET("/", handlerWrapper(indexHandler, globalData))
	router.GET("/:year/:month", handlerWrapper(archiveHandler, globalData))
	router.GET("/:year/:month/:post", handlerWrapper(postHandler, globalData))
	// No tags yet.
	//router.GET("/tag/:tag", handlerWrapper(tagHandler, globalData))
	// No pagination yet.
	//router.GET("/tag/:tag/:page", handlerWrapper(tagHandler, globalData))
	router.GET("/images/*file", handlerWrapper(simpleHandler, globalData))
	router.GET("/assets/*file", handlerWrapper(staticCompressHandler, globalData))
	router.GET("/favicon.ico", handlerWrapper(staticCompressHandler, globalData))

	http.ListenAndServe(":8080", router)
}
<|endoftext|>