<commit_before>package psn

import (
	"bytes"
	"fmt"
	"strconv"
	"strings"

	humanize "github.com/dustin/go-humanize"
)

// parses KiB strings, returns bytes in int64, and humanized bytes.
//
//  KiB = kibibyte = 1024 bytes
//  MiB = mebibyte = 1024 KiB = 1,048,576 bytes
//  GiB = gibibyte = 1024 MiB = 1,073,741,824 bytes
//  TiB = tebibyte = 1024 GiB = 1,099,511,627,776 bytes
//  PiB = pebibyte = 1024 TiB = 1,125,899,906,842,624 bytes
//  EiB = exbibyte = 1024 PiB = 1,152,921,504,606,846,976 bytes
//
func parseTopCommandKiB(s string) (bts uint64, hs string, err error) {
	s = strings.TrimSpace(s)
	switch {
	// suffix 'm' means megabytes
	case strings.HasSuffix(s, "m"):
		ns := s[:len(s)-1]
		var mib float64
		mib, err = strconv.ParseFloat(ns, 64)
		if err != nil {
			return 0, "", err
		}
		bts = uint64(mib) * 1024 * 1024

	// suffix 'g' means gigabytes
	case strings.HasSuffix(s, "g"):
		ns := s[:len(s)-1]
		var gib float64
		gib, err = strconv.ParseFloat(ns, 64)
		if err != nil {
			return 0, "", err
		}
		bts = uint64(gib) * 1024 * 1024 * 1024

	default:
		var kib float64
		kib, err = strconv.ParseFloat(s, 64)
		if err != nil {
			return 0, "", err
		}
		bts = uint64(kib) * 1024
	}

	hs = humanize.Bytes(bts)
	return
}

// TopRowHeaders is the headers in 'top' output.
var TopRowHeaders = []string{
	"PID",
	"USER",
	"PR",
	"NI",
	"VIRT",
	"RES",
	"SHR",
	"S",
	"%CPU",
	"%MEM",
	"TIME+",
	"COMMAND",
}

type topCommandOutputRowIdx int

const (
	top_command_output_row_idx_pid topCommandOutputRowIdx = iota
	top_command_output_row_idx_user
	top_command_output_row_idx_pr
	top_command_output_row_idx_ni
	top_command_output_row_idx_virt
	top_command_output_row_idx_res
	top_command_output_row_idx_shr
	top_command_output_row_idx_s
	top_command_output_row_idx_cpu
	top_command_output_row_idx_mem
	top_command_output_row_idx_time
	top_command_output_row_idx_command
)

var bytesToSkip = [][]byte{
	{116, 111, 112, 32, 45},                     // 'top -'
	{84, 97, 115, 107, 115, 58, 32},             // 'Tasks: '
	{37, 67, 112, 117, 40, 115, 41, 58, 32},     // '%Cpu(s): '
	{67, 112, 117, 40, 115, 41, 58, 32},         // 'Cpu(s): '
	{75, 105, 66, 32, 77, 101, 109, 32, 58, 32}, // 'KiB Mem : '
	{75, 105, 66, 32, 83, 119, 97, 112, 58, 32}, // 'KiB Swap: '
	{80, 73, 68, 32},                            // 'PID '
}

func topRowToSkip(data []byte) bool {
	for _, prefix := range bytesToSkip {
		if bytes.HasPrefix(data, prefix) {
			return true
		}
	}
	return false
}

// ParseTopOutput parses 'top' command output and returns the rows.
func ParseTopOutput(s string) ([]TopCommandRow, error) {
	lines := strings.Split(s, "\n")
	rows := make([][]string, 0, len(lines))
	for _, line := range lines {
		line = strings.TrimSpace(line)
		if len(line) == 0 {
			continue
		}
		if topRowToSkip([]byte(line)) {
			continue
		}

		row := strings.Fields(strings.TrimSpace(line))
		if len(row) != len(TopRowHeaders) {
			return nil, fmt.Errorf("unexpected row column number %v (expected %v)", row, TopRowHeaders)
		}
		rows = append(rows, row)
	}

	type result struct {
		row TopCommandRow
		err error
	}
	rc := make(chan result, len(rows))
	for _, row := range rows {
		go func(row []string) {
			tr, err := parseTopRow(row)
			rc <- result{row: tr, err: err}
		}(row)
	}

	tcRows := make([]TopCommandRow, 0, len(rows))
	for len(tcRows) != len(rows) {
		select {
		case rs := <-rc:
			if rs.err != nil {
				return nil, rs.err
			}
			tcRows = append(tcRows, rs.row)
		}
	}
	return tcRows, nil
}

func parseTopRow(row []string) (TopCommandRow, error) {
	trow := TopCommandRow{
		USER: strings.TrimSpace(row[top_command_output_row_idx_user]),
	}

	pv, err := strconv.ParseInt(row[top_command_output_row_idx_pid], 10, 64)
	if err != nil {
		return TopCommandRow{}, fmt.Errorf("parse error %v (row %v)", err, row)
	}
	trow.PID = pv

	trow.PR = strings.TrimSpace(row[top_command_output_row_idx_pr])
	trow.NI = strings.TrimSpace(row[top_command_output_row_idx_ni])

	virt, virtTxt, err := parseTopCommandKiB(row[top_command_output_row_idx_virt])
	if err != nil {
		return TopCommandRow{}, fmt.Errorf("parse error %v (row %v)", err, row)
	}
	trow.VIRT = row[top_command_output_row_idx_virt]
	trow.VIRTBytesN = virt
	trow.VIRTParsedBytes = virtTxt

	res, resTxt, err := parseTopCommandKiB(row[top_command_output_row_idx_res])
	if err != nil {
		return TopCommandRow{}, fmt.Errorf("parse error %v (row %v)", err, row)
	}
	trow.RES = row[top_command_output_row_idx_res]
	trow.RESBytesN = res
	trow.RESParsedBytes = resTxt

	shr, shrTxt, err := parseTopCommandKiB(row[top_command_output_row_idx_shr])
	if err != nil {
		return TopCommandRow{}, fmt.Errorf("parse error %v (row %v)", err, row)
	}
	trow.SHR = row[top_command_output_row_idx_shr]
	trow.SHRBytesN = shr
	trow.SHRParsedBytes = shrTxt

	trow.S = row[top_command_output_row_idx_s]
	trow.SParsedStatus = convertProcStatus(row[top_command_output_row_idx_s])

	cnum, err := strconv.ParseFloat(row[top_command_output_row_idx_cpu], 64)
	if err != nil {
		return TopCommandRow{}, fmt.Errorf("parse error %v (row %v)", err, row)
	}
	trow.CPUPercent = cnum

	mnum, err := strconv.ParseFloat(row[top_command_output_row_idx_mem], 64)
	if err != nil {
		return TopCommandRow{}, fmt.Errorf("parse error %v (row %v)", err, row)
	}
	trow.MEMPercent = mnum

	trow.TIME = row[top_command_output_row_idx_time]

	return trow, nil
}
<commit_msg>top: add 'Mem: ' edge case<commit_after>package psn

import (
	"bytes"
	"fmt"
	"strconv"
	"strings"

	humanize "github.com/dustin/go-humanize"
)

// parses KiB strings, returns bytes in int64, and humanized bytes.
//
//  KiB = kibibyte = 1024 bytes
//  MiB = mebibyte = 1024 KiB = 1,048,576 bytes
//  GiB = gibibyte = 1024 MiB = 1,073,741,824 bytes
//  TiB = tebibyte = 1024 GiB = 1,099,511,627,776 bytes
//  PiB = pebibyte = 1024 TiB = 1,125,899,906,842,624 bytes
//  EiB = exbibyte = 1024 PiB = 1,152,921,504,606,846,976 bytes
//
func parseTopCommandKiB(s string) (bts uint64, hs string, err error) {
	s = strings.TrimSpace(s)
	switch {
	// suffix 'm' means megabytes
	case strings.HasSuffix(s, "m"):
		ns := s[:len(s)-1]
		var mib float64
		mib, err = strconv.ParseFloat(ns, 64)
		if err != nil {
			return 0, "", err
		}
		bts = uint64(mib) * 1024 * 1024

	// suffix 'g' means gigabytes
	case strings.HasSuffix(s, "g"):
		ns := s[:len(s)-1]
		var gib float64
		gib, err = strconv.ParseFloat(ns, 64)
		if err != nil {
			return 0, "", err
		}
		bts = uint64(gib) * 1024 * 1024 * 1024

	default:
		var kib float64
		kib, err = strconv.ParseFloat(s, 64)
		if err != nil {
			return 0, "", err
		}
		bts = uint64(kib) * 1024
	}

	hs = humanize.Bytes(bts)
	return
}

// TopRowHeaders is the headers in 'top' output.
var TopRowHeaders = []string{
	"PID",
	"USER",
	"PR",
	"NI",
	"VIRT",
	"RES",
	"SHR",
	"S",
	"%CPU",
	"%MEM",
	"TIME+",
	"COMMAND",
}

type topCommandOutputRowIdx int

const (
	top_command_output_row_idx_pid topCommandOutputRowIdx = iota
	top_command_output_row_idx_user
	top_command_output_row_idx_pr
	top_command_output_row_idx_ni
	top_command_output_row_idx_virt
	top_command_output_row_idx_res
	top_command_output_row_idx_shr
	top_command_output_row_idx_s
	top_command_output_row_idx_cpu
	top_command_output_row_idx_mem
	top_command_output_row_idx_time
	top_command_output_row_idx_command
)

var bytesToSkip = [][]byte{
	{116, 111, 112, 32, 45},                     // 'top -'
	{84, 97, 115, 107, 115, 58, 32},             // 'Tasks: '
	{37, 67, 112, 117, 40, 115, 41, 58, 32},     // '%Cpu(s): '
	{67, 112, 117, 40, 115, 41, 58, 32},         // 'Cpu(s): '
	{75, 105, 66, 32, 77, 101, 109, 32, 58, 32}, // 'KiB Mem : '
	{75, 105, 66, 32, 83, 119, 97, 112, 58, 32}, // 'KiB Swap: '
	{77, 101, 109, 58, 32},                      // 'Mem: '
	{80, 73, 68, 32},                            // 'PID '
}

func topRowToSkip(data []byte) bool {
	for _, prefix := range bytesToSkip {
		if bytes.HasPrefix(data, prefix) {
			return true
		}
	}
	return false
}

// ParseTopOutput parses 'top' command output and returns the rows.
func ParseTopOutput(s string) ([]TopCommandRow, error) {
	lines := strings.Split(s, "\n")
	rows := make([][]string, 0, len(lines))
	for _, line := range lines {
		line = strings.TrimSpace(line)
		if len(line) == 0 {
			continue
		}
		if topRowToSkip([]byte(line)) {
			continue
		}

		row := strings.Fields(strings.TrimSpace(line))
		if len(row) != len(TopRowHeaders) {
			return nil, fmt.Errorf("unexpected row column number %v (expected %v)", row, TopRowHeaders)
		}
		rows = append(rows, row)
	}

	type result struct {
		row TopCommandRow
		err error
	}
	rc := make(chan result, len(rows))
	for _, row := range rows {
		go func(row []string) {
			tr, err := parseTopRow(row)
			rc <- result{row: tr, err: err}
		}(row)
	}

	tcRows := make([]TopCommandRow, 0, len(rows))
	for len(tcRows) != len(rows) {
		select {
		case rs := <-rc:
			if rs.err != nil {
				return nil, rs.err
			}
			tcRows = append(tcRows, rs.row)
		}
	}
	return tcRows, nil
}

func parseTopRow(row []string) (TopCommandRow, error) {
	trow := TopCommandRow{
		USER: strings.TrimSpace(row[top_command_output_row_idx_user]),
	}

	pv, err := strconv.ParseInt(row[top_command_output_row_idx_pid], 10, 64)
	if err != nil {
		return TopCommandRow{}, fmt.Errorf("parse error %v (row %v)", err, row)
	}
	trow.PID = pv

	trow.PR = strings.TrimSpace(row[top_command_output_row_idx_pr])
	trow.NI = strings.TrimSpace(row[top_command_output_row_idx_ni])

	virt, virtTxt, err := parseTopCommandKiB(row[top_command_output_row_idx_virt])
	if err != nil {
		return TopCommandRow{}, fmt.Errorf("parse error %v (row %v)", err, row)
	}
	trow.VIRT = row[top_command_output_row_idx_virt]
	trow.VIRTBytesN = virt
	trow.VIRTParsedBytes = virtTxt

	res, resTxt, err := parseTopCommandKiB(row[top_command_output_row_idx_res])
	if err != nil {
		return TopCommandRow{}, fmt.Errorf("parse error %v (row %v)", err, row)
	}
	trow.RES = row[top_command_output_row_idx_res]
	trow.RESBytesN = res
	trow.RESParsedBytes = resTxt

	shr, shrTxt, err := parseTopCommandKiB(row[top_command_output_row_idx_shr])
	if err != nil {
		return TopCommandRow{}, fmt.Errorf("parse error %v (row %v)", err, row)
	}
	trow.SHR = row[top_command_output_row_idx_shr]
	trow.SHRBytesN = shr
	trow.SHRParsedBytes = shrTxt

	trow.S = row[top_command_output_row_idx_s]
	trow.SParsedStatus = convertProcStatus(row[top_command_output_row_idx_s])

	cnum, err := strconv.ParseFloat(row[top_command_output_row_idx_cpu], 64)
	if err != nil {
		return TopCommandRow{}, fmt.Errorf("parse error %v (row %v)", err, row)
	}
	trow.CPUPercent = cnum

	mnum, err := strconv.ParseFloat(row[top_command_output_row_idx_mem], 64)
	if err != nil {
		return TopCommandRow{}, fmt.Errorf("parse error %v (row %v)", err, row)
	}
	trow.MEMPercent = mnum

	trow.TIME = row[top_command_output_row_idx_time]

	return trow, nil
}
<|endoftext|>


<commit_before>// Package pluginmgr is a plugin manager for hal that allows users to
// manage plugins from inside chat or over REST.
package pluginmgr

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"net/http"

	"github.com/codegangsta/cli"
	"github.com/netflix/hal-9001/hal"
)

// NAME of the plugin
const NAME = "pluginmgr"

// HELP text
const HELP = `
Examples:
!plugin list
!plugin save
!plugin attach <plugin> --room <room>
!plugin attach --regex ^!foo <plugin> <room>
!plugin detach <plugin> <room>

e.g.
!plugin attach uptime --room CORE
!plugin detach uptime --room CORE
!plugin save
`

// Register makes this plugin available to the system.
func Register() {
	plugin := hal.Plugin{
		Name:  NAME,
		Func:  pluginmgr,
		Regex: "^!plugin",
	}

	plugin.Register()

	http.HandleFunc("/v1/plugins", httpPlugins)
}

func pluginmgr(evt hal.Evt) {
	// expose plugin names as subcommands so users can do
	// !plugin attach uptime --regex ^!up --room CORE
	attachCmds := make([]cli.Command, 0)
	detachCmds := make([]cli.Command, 0)

	pr := hal.PluginRegistry()

	for _, p := range pr.PluginList() {
		var name, room, regex string
		name = p.Name

		attachCmd := cli.Command{
			Name:  name,
			Usage: fmt.Sprintf("Attach the %s plugin.", p.Name),
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:        "regex",
					Value:       p.Regex,
					Destination: &regex,
					Usage:       "set a regex filter to select messages to send the plugin, overriding the plugin default",
				},
				cli.StringFlag{
					Name:        "room",
					Value:       evt.RoomId, // default to the room where the command originated
					Destination: &room,
					Usage:       "the room to attach the plugin to",
				},
			},
			Action: func(c *cli.Context) {
				attachPlugin(c, &evt, room, name, regex)
			},
		}

		attachCmds = append(attachCmds, attachCmd)

		detachCmd := cli.Command{
			Name:  name,
			Usage: fmt.Sprintf("Attach the %s plugin.", p.Name),
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:        "room",
					Value:       evt.RoomId, // default to the room where the command originated
					Destination: &room,      // should be safe to use this again...
					Usage:       "the room to detach from",
				},
			},
			Action: func(c *cli.Context) {
				detachPlugin(c, &evt, room, name)
			},
		}

		detachCmds = append(detachCmds, detachCmd)
	}

	listAttached := false
	listDetached := false

	// have cli write output to a buffer instead of stdio
	outbuf := bytes.NewBuffer([]byte{})

	app := cli.NewApp()
	app.Name = NAME
	app.HelpName = NAME
	app.Usage = "manage plugin instances"
	app.Writer = outbuf
	app.Commands = []cli.Command{
		{
			Name:  "list",
			Usage: "list the available plugins",
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name:        "attached",
					Destination: &listAttached,
					Usage:       "only show attached plugins",
				},
				cli.BoolFlag{
					Name:        "detached",
					Destination: &listDetached,
					Usage:       "only show detached plugins",
				},
			},
			Action: func(c *cli.Context) {
				listPlugins(c, &evt, listAttached, listDetached)
			},
		},
		{
			Name:  "save",
			Usage: "save the runtime plugin configuration",
			Action: func(c *cli.Context) {
				savePlugins(c, &evt)
			},
		},
		{
			Name:        "attach",
			Usage:       "attach a plugin to a room (creates an instance)",
			Subcommands: attachCmds, // composed above
		},
		// for now, plugins are restricted to one instance per room to avoid having to
		// generate and manage some kind of ID, which will probably get added later
		{
			Name:        "detach",
			Usage:       "detach a plugin from a room",
			Subcommands: detachCmds,
		},
	}

	err := app.Run(evt.BodyAsArgv())
	if err != nil {
		log.Fatalf("Command parsing failed: %s", err)
	}

	evt.Reply(outbuf.String())
}

func listPlugins(c *cli.Context, evt *hal.Evt, attached bool, detached bool) {
	pr := hal.PluginRegistry()
	buf := bytes.NewBufferString("\n")
	var plugins []*hal.Plugin

	if attached {
		plugins = pr.ActivePluginList()
	} else if detached {
		plugins = pr.InactivePluginList()
	} else {
		plugins = pr.PluginList()
	}

	for _, p := range plugins {
		// TODO: better formatting
		buf.WriteString("--> ")
		buf.WriteString(p.String())
		buf.WriteString("\n")
	}

	evt.Reply(buf.String())
}

func savePlugins(c *cli.Context, evt *hal.Evt) {
	pr := hal.PluginRegistry()

	err := pr.SaveInstances()
	if err != nil {
		evt.Replyf("Error while saving plugin config: %s", err)
	} else {
		evt.Reply("Plugin configuration saved.")
	}
}

func roomToId(evt *hal.Evt, room string) string {
	// the user may have provided --room with a room name
	// try to resolve a roomId with the broker, falling back to the name
	if evt.Broker != nil {
		roomId := evt.Broker.RoomNameToId(room)
		if roomId == "" {
			return room
		}
	}

	return room
}

func attachPlugin(c *cli.Context, evt *hal.Evt, room, pluginName, regex string) {
	pr := hal.PluginRegistry()
	plugin := pr.GetPlugin(pluginName)
	if plugin == nil {
		evt.Replyf("No such plugin: '%s'", plugin)
		return
	}

	roomId := roomToId(evt, room)
	inst := plugin.Instance(roomId, evt.Broker)
	inst.RoomId = roomId
	inst.Regex = regex
	err := inst.Register()
	if err != nil {
		evt.Replyf("Failed to launch plugin '%s' in room id '%s': %s", plugin, roomId, err)

	} else {
		evt.Replyf("Launched an instance of plugin: '%s' in room id '%s'", plugin, roomId)
	}
}

func detachPlugin(c *cli.Context, evt *hal.Evt, room, plugin string) {
	pr := hal.PluginRegistry()
	roomId := roomToId(evt, room)
	instances := pr.FindInstances(roomId, evt.BrokerName(), plugin)

	// there should be only one, for now just log if that is not the case
	if len(instances) > 1 {
		log.Printf("FindInstances(%q, %q) returned %d instances. Expected 0 or 1.",
			room, plugin, len(instances))
	}

	for _, inst := range instances {
		inst.Unregister()
		evt.Replyf("%q/%q unregistered", room, plugin)
	}
}

func httpPlugins(w http.ResponseWriter, r *http.Request) {
	pr := hal.PluginRegistry()
	plugins := pr.PluginList()
	js, err := json.Marshal(plugins)
	if err != nil {
		log.Fatalf("Failed to marshal plugin list to JSON: %s", err)
	}
	w.Write(js)
}
<commit_msg>clean up and use the table functions in responses<commit_after>// Package pluginmgr is a plugin manager for hal that allows users to
// manage plugins from inside chat or over REST.
package pluginmgr

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"net/http"

	"github.com/codegangsta/cli"
	"github.com/netflix/hal-9001/hal"
)

// NAME of the plugin
const NAME = "pluginmgr"

// HELP text
const HELP = `
Examples:
!plugin list
!plugin instances
!plugin save
!plugin attach <plugin> --room <room>
!plugin attach --regex ^!foo <plugin> <room>
!plugin detach <plugin> <room>

e.g.
!plugin attach uptime --room CORE
!plugin detach uptime --room CORE
!plugin save
`

// Register makes this plugin available to the system.
func Register() {
	plugin := hal.Plugin{
		Name:  NAME,
		Func:  pluginmgr,
		Regex: "^!plugin",
	}

	plugin.Register()

	http.HandleFunc("/v1/plugins", httpPlugins)
}

func pluginmgr(evt hal.Evt) {
	// expose plugin names as subcommands so users can do
	// !plugin attach uptime --regex ^!up --room CORE
	attachCmds := make([]cli.Command, 0)
	detachCmds := make([]cli.Command, 0)

	pr := hal.PluginRegistry()

	for _, p := range pr.PluginList() {
		var name, room, regex string
		name = p.Name

		attachCmd := cli.Command{
			Name:  name,
			Usage: fmt.Sprintf("Attach the %s plugin.", p.Name),
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:        "regex",
					Value:       p.Regex,
					Destination: &regex,
					Usage:       "set a regex filter to select messages to send the plugin, overriding the plugin default",
				},
				cli.StringFlag{
					Name:        "room",
					Value:       evt.RoomId, // default to the room where the command originated
					Destination: &room,
					Usage:       "the room to attach the plugin to",
				},
			},
			Action: func(c *cli.Context) {
				attachPlugin(c, &evt, room, name, regex)
			},
		}

		attachCmds = append(attachCmds, attachCmd)

		detachCmd := cli.Command{
			Name:  name,
			Usage: fmt.Sprintf("Attach the %s plugin.", p.Name),
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:        "room",
					Value:       evt.RoomId, // default to the room where the command originated
					Destination: &room,      // should be safe to use this again...
					Usage:       "the room to detach from",
				},
			},
			Action: func(c *cli.Context) {
				detachPlugin(c, &evt, room, name)
			},
		}

		detachCmds = append(detachCmds, detachCmd)
	}

	// have cli write output to a buffer instead of stdio
	outbuf := bytes.NewBuffer([]byte{})

	app := cli.NewApp()
	app.Name = NAME
	app.HelpName = NAME
	app.Usage = "manage plugin instances"
	app.Writer = outbuf
	app.Commands = []cli.Command{
		{
			Name:   "list",
			Usage:  "list the available plugins",
			Action: func(c *cli.Context) { listPlugins(c, &evt) },
		},
		{
			Name:   "instances",
			Usage:  "list the currently attached and running plugins",
			Action: func(c *cli.Context) { listInstances(c, &evt) },
		},
		{
			Name:   "save",
			Usage:  "save the runtime plugin configuration",
			Action: func(c *cli.Context) { savePlugins(c, &evt) },
		},
		{
			Name:        "attach",
			Usage:       "attach a plugin to a room (creates an instance)",
			Subcommands: attachCmds, // composed above
		},
		// for now, plugins are restricted to one instance per room to avoid having to
		// generate and manage some kind of ID, which will probably get added later
		{
			Name:        "detach",
			Usage:       "detach a plugin from a room",
			Subcommands: detachCmds,
		},
	}

	err := app.Run(evt.BodyAsArgv())
	if err != nil {
		log.Fatalf("Command parsing failed: %s", err)
	}

	evt.Reply(outbuf.String())
}

func listPlugins(c *cli.Context, evt *hal.Evt) {
	hdr := []string{"Plugin Name", "Default RE", "Status"}
	rows := [][]string{}
	pr := hal.PluginRegistry()

	for _, p := range pr.ActivePluginList() {
		row := []string{p.Name, p.Regex, "active"}
		rows = append(rows, row)
	}

	for _, p := range pr.InactivePluginList() {
		row := []string{p.Name, p.Regex, "inactive"}
		rows = append(rows, row)
	}

	evt.ReplyTable(hdr, rows)
}

func listInstances(c *cli.Context, evt *hal.Evt) {
	hdr := []string{"Plugin Name", "Broker", "Room", "RE"}
	rows := [][]string{}
	pr := hal.PluginRegistry()

	for _, inst := range pr.InstanceList() {
		row := []string{
			inst.Plugin.Name,
			inst.Broker.Name(),
			inst.RoomId,
			inst.Regex,
		}
		rows = append(rows, row)
	}

	evt.ReplyTable(hdr, rows)
}

func savePlugins(c *cli.Context, evt *hal.Evt) {
	pr := hal.PluginRegistry()

	err := pr.SaveInstances()
	if err != nil {
		evt.Replyf("Error while saving plugin config: %s", err)
	} else {
		evt.Reply("Plugin configuration saved.")
	}
}

func roomToId(evt *hal.Evt, room string) string {
	// the user may have provided --room with a room name
	// try to resolve a roomId with the broker, falling back to the name
	if evt.Broker != nil {
		roomId := evt.Broker.RoomNameToId(room)
		if roomId == "" {
			return room
		}
	}

	return room
}

func attachPlugin(c *cli.Context, evt *hal.Evt, room, pluginName, regex string) {
	pr := hal.PluginRegistry()
	plugin := pr.GetPlugin(pluginName)
	if plugin == nil {
		evt.Replyf("No such plugin: '%s'", plugin)
		return
	}

	roomId := roomToId(evt, room)
	inst := plugin.Instance(roomId, evt.Broker)
	inst.RoomId = roomId
	inst.Regex = regex
	err := inst.Register()
	if err != nil {
		evt.Replyf("Failed to launch plugin '%s' in room id '%s': %s", plugin, roomId, err)

	} else {
		evt.Replyf("Launched an instance of plugin: '%s' in room id '%s'", plugin, roomId)
	}
}

func detachPlugin(c *cli.Context, evt *hal.Evt, room, plugin string) {
	pr := hal.PluginRegistry()
	roomId := roomToId(evt, room)
	instances := pr.FindInstances(roomId, evt.BrokerName(), plugin)

	// there should be only one, for now just log if that is not the case
	if len(instances) > 1 {
		log.Printf("FindInstances(%q, %q) returned %d instances. Expected 0 or 1.",
			room, plugin, len(instances))
	}

	for _, inst := range instances {
		inst.Unregister()
		evt.Replyf("%q/%q unregistered", room, plugin)
	}
}

func httpPlugins(w http.ResponseWriter, r *http.Request) {
	pr := hal.PluginRegistry()
	plugins := pr.PluginList()
	js, err := json.Marshal(plugins)
	if err != nil {
		log.Fatalf("Failed to marshal plugin list to JSON: %s", err)
	}
	w.Write(js)
}
<|endoftext|>


<commit_before>// Copyright 2017 The Cockroach 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. See the AUTHORS file
// for names of contributors.

package main

import (
	"database/sql"
	"math/rand"

	"context"

	"github.com/cockroachdb/cockroach-go/crdb"
)

// 2.8 The Stock-Level Transaction
//
// The Stock-Level business transaction determines the number of recently sold
// items that have a stock level below a specified threshold. It represents a
// heavy read-only database transaction with a low frequency of execution, a
// relaxed response time requirement, and relaxed consistency requirements.

// 2.8.2.3 states:
// Full serializability and repeatable reads are not required for the
// Stock-Level business transaction. All data read must be committed and no
// older than the most recently committed data prior to the time this business
// transaction was initiated. All other ACID properties must be maintained.
// TODO(jordan): can we take advantage of this?

type stockLevelData struct {
	// This data must all be returned by the transaction. See 2.8.3.4.
	dID       int
	threshold int
	lowStock  int
}

type stockLevel struct{}

var _ tpccTx = stockLevel{}

func (s stockLevel) run(db *sql.DB, wID int) (interface{}, error) {
	// 2.8.1.2: The threshold of minimum quantity in stock is selected at random
	// within [10..20].
	d := stockLevelData{
		threshold: randInt(10, 20),
		dID:       rand.Intn(9) + 1,
	}

	// This is the only join in the application, so we don't need to worry about
	// this setting persisting incorrectly across queries.
	if _, err := db.Exec(`set experimental_force_lookup_join=true`); err != nil {
		return nil, err
	}

	if err := crdb.ExecuteTx(
		context.Background(),
		db,
		txOpts,
		func(tx *sql.Tx) error {
			var dNextOID int
			if err := tx.QueryRow(`
				SELECT d_next_o_id
				FROM district
				WHERE d_w_id = $1 AND d_id = $2`,
				wID, d.dID,
			).Scan(&dNextOID); err != nil {
				return err
			}

			// Count the number of recently sold items that have a stock level below
			// the threshold.
			return tx.QueryRow(`
				SELECT COUNT(DISTINCT(s_i_id))
				FROM order_line
				JOIN stock
				ON s_i_id=ol_i_id
				  AND s_w_id=ol_w_id
				WHERE ol_w_id = $1
				  AND ol_d_id = $2
				  AND ol_o_id BETWEEN $3 - 20 AND $3 - 1
				  AND s_quantity < $4`,
				wID, d.dID, dNextOID, d.threshold,
			).Scan(&d.lowStock)
		}); err != nil {
		return nil, err
	}
	return d, nil
}
<commit_msg>Force lookup joins on the transaction connection<commit_after>// Copyright 2017 The Cockroach 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. See the AUTHORS file
// for names of contributors.

package main

import (
	"database/sql"
	"math/rand"

	"context"

	"github.com/cockroachdb/cockroach-go/crdb"
)

// 2.8 The Stock-Level Transaction
//
// The Stock-Level business transaction determines the number of recently sold
// items that have a stock level below a specified threshold. It represents a
// heavy read-only database transaction with a low frequency of execution, a
// relaxed response time requirement, and relaxed consistency requirements.

// 2.8.2.3 states:
// Full serializability and repeatable reads are not required for the
// Stock-Level business transaction. All data read must be committed and no
// older than the most recently committed data prior to the time this business
// transaction was initiated. All other ACID properties must be maintained.
// TODO(jordan): can we take advantage of this?

type stockLevelData struct {
	// This data must all be returned by the transaction. See 2.8.3.4.
	dID       int
	threshold int
	lowStock  int
}

type stockLevel struct{}

var _ tpccTx = stockLevel{}

func (s stockLevel) run(db *sql.DB, wID int) (interface{}, error) {
	// 2.8.1.2: The threshold of minimum quantity in stock is selected at random
	// within [10..20].
	d := stockLevelData{
		threshold: randInt(10, 20),
		dID:       rand.Intn(9) + 1,
	}

	if err := crdb.ExecuteTx(
		context.Background(),
		db,
		txOpts,
		func(tx *sql.Tx) error {
			// This is the only join in the application, so we don't need to worry about
			// this setting persisting incorrectly across queries.
			if _, err := tx.Exec(`set experimental_force_lookup_join=true`); err != nil {
				return err
			}

			var dNextOID int
			if err := tx.QueryRow(`
				SELECT d_next_o_id
				FROM district
				WHERE d_w_id = $1 AND d_id = $2`,
				wID, d.dID,
			).Scan(&dNextOID); err != nil {
				return err
			}

			// Count the number of recently sold items that have a stock level below
			// the threshold.
			return tx.QueryRow(`
				SELECT COUNT(DISTINCT(s_i_id))
				FROM order_line
				JOIN stock
				ON s_i_id=ol_i_id
				  AND s_w_id=ol_w_id
				WHERE ol_w_id = $1
				  AND ol_d_id = $2
				  AND ol_o_id BETWEEN $3 - 20 AND $3 - 1
				  AND s_quantity < $4`,
				wID, d.dID, dNextOID, d.threshold,
			).Scan(&d.lowStock)
		}); err != nil {
		return nil, err
	}
	return d, nil
}
<|endoftext|>


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

package blockchain

import (
	"testing"
)

func TestGetUnconfirmedTransactions(t *testing.T) {
	c := New()
	txs, e := c.GetUnconfirmedTransactions()
	if e != nil {
		t.Fatal(e)
	}

	for i := range txs.Txs {
		tx := txs.Txs[i]

		if len(tx.Hash) != 64 {
			t.Fatal("Wrong length value on field 'Hash'")
		}

		if tx.Ver < 1 {
			t.Fatal("Wrong value on field 'Ver'")
		}

		if tx.TxIndex < 1 {
			t.Fatal("Wrong value on field 'TxIndex'")
		}

		if tx.Time < 1 {
			t.Fatal("Wrong value on field 'Time'")
		}

		if len(tx.Inputs) < 1 {
			t.Fatal("Wrong count items on field 'Inputs'")
		}

		if len(tx.Out) < 1 {
			t.Fatal("Wrong count items on field 'Out'")
		}
	}
}

func TestGetTransaction(t *testing.T) {
	c := New()
	tx, e := c.GetTransaction("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b")
	if e != nil {
		t.Fatal(e)
	}
	checkFirstTx(t, tx)
}

func checkFirstTx(t *testing.T, tx *Tx) {
	if tx.Hash != "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b" {
		t.Fatal("Wrong value on field 'Tx[0].Hash'")
	}

	if tx.LockTime != 0 {
		t.Fatal("Wrong value on field 'Tx[0].LockTime'")
	}

	if tx.Ver != 1 {
		t.Fatal("Wrong value on field 'Tx[0].Ver'")
	}

	if tx.Size != 204 {
		t.Fatal("Wrong value on field 'Tx[0].Size'")
	}

	if tx.Weight != 816 {
		t.Fatal("Wrong value on field 'Tx[0].Weight'")
	}

	if tx.Time != 1231006505 {
		t.Fatal("Wrong value on field 'Tx[0].Time'")
	}

	if tx.TxIndex != 14849 {
		t.Fatal("Wrong value on field 'Tx[0].TxIndex'")
	}

	if tx.VinSz != 1 {
		t.Fatal("Wrong value on field 'Tx[0].VinSz'")
	}

	if tx.RelayedBy != "0.0.0.0" {
		t.Fatal("Wrong value on field 'Tx[0].RelayedBy'")
	}

	inputs0 := tx.Inputs[0]

	if inputs0.Sequence != 4294967295 {
		t.Fatal("Wrong value on field 'Tx[0].Inputs[0].Sequence'")
	}

	inputScript := "04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6"
	inputScript += "f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73"
	if inputs0.Script != inputScript {
		t.Fatal("Wrong value on field 'Tx[0].Inputs[0].Script'")
	}

	out0 := tx.Out[0]

	if out0.Spent != false {
		t.Fatal("Wrong value on field 'Tx[0].Out[0].Spent'")
	}

	if out0.TxIndex != 14849 {
		t.Fatal("Wrong value on field 'Tx[0].Out[0].TxIndex'")
	}

	if out0.Type != 0 {
		t.Fatal("Wrong value on field 'Tx[0].Out[0].Type'")
	}

	if out0.Addr != "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa" {
		t.Fatal("Wrong value on field 'Tx[0].Out[0].Addr'")
	}

	if out0.Value != 5000000000 {
		t.Fatal("Wrong value on field 'Tx[0].Out[0].Value'")
	}

	if out0.N != 0 {
		t.Fatal("Wrong value on field 'Tx[0].Out[0].N'")
	}

	outScript := "4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb"
	outScript += "649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac"
	if out0.Script != outScript {
		t.Fatal("Wrong value on field 'Tx[0].Out[0].N'")
	}
}
<commit_msg>refactor(transaction): Made checking of the first output in a separate function<commit_after>// Copyright 2017 Vasilyuk Vasiliy. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package blockchain

import "testing"

func TestGetUnconfirmedTransactions(t *testing.T) {
	c := New()
	txs, e := c.GetUnconfirmedTransactions()
	if e != nil {
		t.Fatal(e)
	}

	for i := range txs.Txs {
		tx := txs.Txs[i]

		if len(tx.Hash) != 64 {
			t.Fatal("Wrong length value on field 'Hash'")
		}

		if tx.Ver < 1 {
			t.Fatal("Wrong value on field 'Ver'")
		}

		if tx.TxIndex < 1 {
			t.Fatal("Wrong value on field 'TxIndex'")
		}

		if tx.Time < 1 {
			t.Fatal("Wrong value on field 'Time'")
		}

		if len(tx.Inputs) < 1 {
			t.Fatal("Wrong count items on field 'Inputs'")
		}

		if len(tx.Out) < 1 {
			t.Fatal("Wrong count items on field 'Out'")
		}
	}
}

func TestGetTransaction(t *testing.T) {
	c := New()
	tx, e := c.GetTransaction("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b")
	if e != nil {
		t.Fatal(e)
	}
	checkFirstTx(t, tx)
}

func checkFirstTx(t *testing.T, tx *Tx) {
	t.Logf("Started: checkFirstTx")

	if tx.Hash != "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b" {
		t.Fatal("Wrong value on field 'Tx[0].Hash'")
	}

	if tx.LockTime != 0 {
		t.Fatal("Wrong value on field 'Tx[0].LockTime'")
	}

	if tx.Ver != 1 {
		t.Fatal("Wrong value on field 'Tx[0].Ver'")
	}

	if tx.Size != 204 {
		t.Fatal("Wrong value on field 'Tx[0].Size'")
	}

	if tx.Weight != 816 {
		t.Fatal("Wrong value on field 'Tx[0].Weight'")
	}

	if tx.Time != 1231006505 {
		t.Fatal("Wrong value on field 'Tx[0].Time'")
	}

	if tx.TxIndex != 14849 {
		t.Fatal("Wrong value on field 'Tx[0].TxIndex'")
	}

	if tx.VinSz != 1 {
		t.Fatal("Wrong value on field 'Tx[0].VinSz'")
	}

	if tx.RelayedBy != "0.0.0.0" {
		t.Fatal("Wrong value on field 'Tx[0].RelayedBy'")
	}

	inputs0 := tx.Inputs[0]

	if inputs0.Sequence != 4294967295 {
		t.Fatal("Wrong value on field 'Tx[0].Inputs[0].Sequence'")
	}

	inputScript := "04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6"
	inputScript += "f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73"
	if inputs0.Script != inputScript {
		t.Fatal("Wrong value on field 'Tx[0].Inputs[0].Script'")
	}

	checkFirstTxOut(t, tx.Out[0])

}

func checkFirstTxOut(t *testing.T, out *Out) {
	t.Logf("Started: checkFirstTxOut")

	if out.Spent != false {
		t.Fatal("Wrong value on field 'Tx[0].Out[0].Spent'")
	}

	if out.TxIndex != 14849 {
		t.Fatal("Wrong value on field 'Tx[0].Out[0].TxIndex'")
	}

	if out.Type != 0 {
		t.Fatal("Wrong value on field 'Tx[0].Out[0].Type'")
	}

	if out.Addr != "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa" {
		t.Fatal("Wrong value on field 'Tx[0].Out[0].Addr'")
	}

	if out.Value != 5000000000 {
		t.Fatal("Wrong value on field 'Tx[0].Out[0].Value'")
	}

	if out.N != 0 {
		t.Fatal("Wrong value on field 'Tx[0].Out[0].N'")
	}

	outScript := "4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb"
	outScript += "649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac"
	if out.Script != outScript {
		t.Fatal("Wrong value on field 'Tx[0].Out[0].N'")
	}
}
<|endoftext|>


<commit_before>package triemux

import (
	"io/ioutil"
	"math/rand"
	"net/http"
	"strings"
	"time"
	"testing"
)

func init() {
	rand.Seed(time.Now().UTC().UnixNano())
}

type SplitExample struct {
	in  string
	out []string
}

var splitExamples = []SplitExample{
	{"", []string{}},
	{"/", []string{}},
	{"foo", []string{"foo"}},
	{"/foo", []string{"foo"}},
	{"/füßball", []string{"füßball"}},
	{"/foo/bar", []string{"foo", "bar"}},
	{"///foo/bar", []string{"foo", "bar"}},
	{"foo/bar", []string{"foo", "bar"}},
	{"/foo/bar/", []string{"foo", "bar", ""}},
	{"/foo//bar/", []string{"foo", "", "bar", ""}},
}

func TestSplitpath(t *testing.T) {
	for _, ex := range splitExamples {
		testSplitpath(t, ex)
	}
}

func testSplitpath(t *testing.T, ex SplitExample) {
	out := splitpath(ex.in)
	if len(out) != len(ex.out) {
		t.Errorf("splitpath(%v) was not %v", ex.in, ex.out)
	}
	for i := range ex.out {
		if out[i] != ex.out[i] {
			t.Errorf("splitpath(%v) differed from %v at component %d "+
				"(expected %v, got %v)", out, ex.out, i, ex.out[i], out[i])
		}
	}
}

type DummyHandler struct {}
func (dh *DummyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {}

var a, b, c *DummyHandler = &DummyHandler{}, &DummyHandler{}, &DummyHandler{}

type Registration struct {
	path    string
	prefix  bool
	handler http.Handler
}

type Check struct {
	path    string
	ok      bool
	handler http.Handler
}

type LookupExample struct {
	registrations []Registration
	checks        []Check
}

var lookupExamples = []LookupExample{
	{ // simple routes
		registrations: []Registration{
			{"/foo", false, a},
			{"/bar", false, b},
		},
		checks: []Check{
			{"/foo", true, a},
			{"/bar", true, b},
			{"/baz", false, nil},
		},
	},
	{ // a prefix route
		registrations: []Registration{
			{"/foo", true, a},
			{"/bar", false, b},
		},
		checks: []Check{
			{"/foo", true, a},
			{"/bar", true, b},
			{"/baz", false, nil},
			{"/foo/bar", true, a},
		},
	},
	{ // a prefix route with an exact route child
		registrations: []Registration{
			{"/foo", true, a},
			{"/foo/bar", false, b},
		},
		checks: []Check{
			{"/foo", true, a},
			{"/foo/baz", true, a},
			{"/foo/bar", true, b},
			{"/foo/bar/bat", true, a},
		},
	},
	{ // a prefix route with an exact route child with a prefix route child
		registrations: []Registration{
			{"/foo", true, a},
			{"/foo/bar", false, b},
			{"/foo/bar/baz", true, c},
		},
		checks: []Check{
			{"/foo", true, a},
			{"/foo/baz", true, a},
			{"/foo/bar", true, b},
			{"/foo/bar/bat", true, a},
			{"/foo/bar/baz", true, c},
			{"/foo/bar/baz/qux", true, c},
		},
	},
	{ // prefix route on the root
		registrations: []Registration{
			{"/", true, a},
		},
		checks: []Check{
			{"/anything", true, a},
			{"", true, a},
			{"/the/hell", true, a},
			{"///you//", true, a},
			{"!like!", true, a},
		},
	},
	{ // exact route on the root
		registrations: []Registration{
			{"/", false, a},
			{"/foo", false, b},
		},
		checks: []Check{
			{"/", true, a},
			{"/foo", true, b},
			{"/bar", false, nil},
		},
	},
}

func TestLookup(t *testing.T) {
	for _, ex := range lookupExamples {
		testLookup(t, ex)
	}
}

func testLookup(t *testing.T, ex LookupExample) {
	mux := NewMux()
	for _, r := range ex.registrations {
		t.Logf("Register(path:%v, prefix:%v, handler:%v)", r.path, r.prefix, r.handler)
		mux.Handle(r.path, r.prefix, r.handler)
	}
	for _, c := range ex.checks {
		handler, ok := mux.lookup(c.path)
		if ok != c.ok {
			t.Errorf("Expected lookup(%v) ok to be %v, was %v", c.path, c.ok, ok)
		}
		if handler != c.handler {
			t.Errorf("Expected lookup(%v) to map to handler %v, was %v", c.path, c.handler, handler)
		}
	}
}

func loadStrings(filename string) []string {
	content, err := ioutil.ReadFile("testdata/routes")
	if err != nil {
		panic(err)
	}
	return strings.Split(string(content), "\n")
}

func benchSetup() *Mux {
	routes := loadStrings("testdata/routes")

	tm := NewMux()
	tm.Handle("/government", true, a)

	for _, l := range routes {
		tm.Handle(l, false, b)
	}
	return tm
}

// Test behaviour looking up extant urls
func BenchmarkLookup(b *testing.B) {
	b.StopTimer()
	tm := benchSetup()
	urls := loadStrings("testdata/urls")
	perm := rand.Perm(len(urls))
	b.StartTimer()

	for i := 0; i < b.N; i++ {
		tm.lookup(urls[perm[i%len(urls)]])
	}
}

// Test behaviour when looking up nonexistent urls
func BenchmarkLookupBogus(b *testing.B) {
	b.StopTimer()
	tm := benchSetup()
	urls := loadStrings("testdata/bogus")
	perm := rand.Perm(len(urls))
	b.StartTimer()

	for i := 0; i < b.N; i++ {
		tm.lookup(urls[perm[i%len(urls)]])
	}
}

// Test worst-case lookup behaviour (see comment in findlongestmatch for
// details)
func BenchmarkLookupMalicious(b *testing.B) {
	b.StopTimer()
	tm := benchSetup()
	b.StartTimer()

	for i := 0; i < b.N; i++ {
		tm.lookup("/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/")
	}
}
<commit_msg>Ensure DummyHandler instances are different.<commit_after>package triemux

import (
	"io/ioutil"
	"math/rand"
	"net/http"
	"strings"
	"time"
	"testing"
)

func init() {
	rand.Seed(time.Now().UTC().UnixNano())
}

type SplitExample struct {
	in  string
	out []string
}

var splitExamples = []SplitExample{
	{"", []string{}},
	{"/", []string{}},
	{"foo", []string{"foo"}},
	{"/foo", []string{"foo"}},
	{"/füßball", []string{"füßball"}},
	{"/foo/bar", []string{"foo", "bar"}},
	{"///foo/bar", []string{"foo", "bar"}},
	{"foo/bar", []string{"foo", "bar"}},
	{"/foo/bar/", []string{"foo", "bar", ""}},
	{"/foo//bar/", []string{"foo", "", "bar", ""}},
}

func TestSplitpath(t *testing.T) {
	for _, ex := range splitExamples {
		testSplitpath(t, ex)
	}
}

func testSplitpath(t *testing.T, ex SplitExample) {
	out := splitpath(ex.in)
	if len(out) != len(ex.out) {
		t.Errorf("splitpath(%v) was not %v", ex.in, ex.out)
	}
	for i := range ex.out {
		if out[i] != ex.out[i] {
			t.Errorf("splitpath(%v) differed from %v at component %d "+
				"(expected %v, got %v)", out, ex.out, i, ex.out[i], out[i])
		}
	}
}

type DummyHandler struct {
  id string
}
func (dh *DummyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {}

var a, b, c *DummyHandler = &DummyHandler{"a"}, &DummyHandler{"b"}, &DummyHandler{"c"}

type Registration struct {
	path    string
	prefix  bool
	handler http.Handler
}

type Check struct {
	path    string
	ok      bool
	handler http.Handler
}

type LookupExample struct {
	registrations []Registration
	checks        []Check
}

var lookupExamples = []LookupExample{
	{ // simple routes
		registrations: []Registration{
			{"/foo", false, a},
			{"/bar", false, b},
		},
		checks: []Check{
			{"/foo", true, a},
			{"/bar", true, b},
			{"/baz", false, nil},
		},
	},
	{ // a prefix route
		registrations: []Registration{
			{"/foo", true, a},
			{"/bar", false, b},
		},
		checks: []Check{
			{"/foo", true, a},
			{"/bar", true, b},
			{"/baz", false, nil},
			{"/foo/bar", true, a},
		},
	},
	{ // a prefix route with an exact route child
		registrations: []Registration{
			{"/foo", true, a},
			{"/foo/bar", false, b},
		},
		checks: []Check{
			{"/foo", true, a},
			{"/foo/baz", true, a},
			{"/foo/bar", true, b},
			{"/foo/bar/bat", true, a},
		},
	},
	{ // a prefix route with an exact route child with a prefix route child
		registrations: []Registration{
			{"/foo", true, a},
			{"/foo/bar", false, b},
			{"/foo/bar/baz", true, c},
		},
		checks: []Check{
			{"/foo", true, a},
			{"/foo/baz", true, a},
			{"/foo/bar", true, b},
			{"/foo/bar/bat", true, a},
			{"/foo/bar/baz", true, c},
			{"/foo/bar/baz/qux", true, c},
		},
	},
	{ // prefix route on the root
		registrations: []Registration{
			{"/", true, a},
		},
		checks: []Check{
			{"/anything", true, a},
			{"", true, a},
			{"/the/hell", true, a},
			{"///you//", true, a},
			{"!like!", true, a},
		},
	},
	{ // exact route on the root
		registrations: []Registration{
			{"/", false, a},
			{"/foo", false, b},
		},
		checks: []Check{
			{"/", true, a},
			{"/foo", true, b},
			{"/bar", false, nil},
		},
	},
}

func TestLookup(t *testing.T) {
	for _, ex := range lookupExamples {
		testLookup(t, ex)
	}
}

func testLookup(t *testing.T, ex LookupExample) {
	mux := NewMux()
	for _, r := range ex.registrations {
		t.Logf("Register(path:%v, prefix:%v, handler:%v)", r.path, r.prefix, r.handler)
		mux.Handle(r.path, r.prefix, r.handler)
	}
	for _, c := range ex.checks {
		handler, ok := mux.lookup(c.path)
		if ok != c.ok {
			t.Errorf("Expected lookup(%v) ok to be %v, was %v", c.path, c.ok, ok)
		}
		if handler != c.handler {
			t.Errorf("Expected lookup(%v) to map to handler %v, was %v", c.path, c.handler, handler)
		}
	}
}

func loadStrings(filename string) []string {
	content, err := ioutil.ReadFile("testdata/routes")
	if err != nil {
		panic(err)
	}
	return strings.Split(string(content), "\n")
}

func benchSetup() *Mux {
	routes := loadStrings("testdata/routes")

	tm := NewMux()
	tm.Handle("/government", true, a)

	for _, l := range routes {
		tm.Handle(l, false, b)
	}
	return tm
}

// Test behaviour looking up extant urls
func BenchmarkLookup(b *testing.B) {
	b.StopTimer()
	tm := benchSetup()
	urls := loadStrings("testdata/urls")
	perm := rand.Perm(len(urls))
	b.StartTimer()

	for i := 0; i < b.N; i++ {
		tm.lookup(urls[perm[i%len(urls)]])
	}
}

// Test behaviour when looking up nonexistent urls
func BenchmarkLookupBogus(b *testing.B) {
	b.StopTimer()
	tm := benchSetup()
	urls := loadStrings("testdata/bogus")
	perm := rand.Perm(len(urls))
	b.StartTimer()

	for i := 0; i < b.N; i++ {
		tm.lookup(urls[perm[i%len(urls)]])
	}
}

// Test worst-case lookup behaviour (see comment in findlongestmatch for
// details)
func BenchmarkLookupMalicious(b *testing.B) {
	b.StopTimer()
	tm := benchSetup()
	b.StartTimer()

	for i := 0; i < b.N; i++ {
		tm.lookup("/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/")
	}
}
<|endoftext|>


<commit_before>package tumblr

import (
	"strconv"
	"strings"
	"testing"
)

func TestPost(t *testing.T) {
	post := Post{
		1234,
		"title",
		"url",
		"http://placehold.it/350x150",
		123,
	}
	if post.ID != 1234 {
		t.Fail()
	}
	if post.Title != "title" {
		t.Fail()
	}
	if post.URL != "url" {
		t.Fail()
	}
	if post.Image != "http://placehold.it/350x150" {
		t.Fail()
	}
	if post.Likes != 123 {
		t.Fail()
	}
}

func TestCSVToPost(t *testing.T) {
	row := make([]string, 5)
	row[0] = "1234"
	row[1] = "title"
	row[2] = "url"
	row[3] = "http://placehold.it/350x150"
	row[4] = "123"
	post := CSVToPost(row)
	if post.ID != 1234 {
		t.Fail()
	}
	if post.Title != "title" {
		t.Fail()
	}
	if post.URL != "url" {
		t.Fail()
	}
	if post.Image != "http://placehold.it/350x150" {
		t.Fail()
	}
	if post.Likes != 123 {
		t.Fail()
	}
}

func TestCorruptPost(t *testing.T) {
	row := make([]string, 5)
	post := CSVToPost(row)
	if post.ID != 0 {
		t.Fail()
	}
}

func TestInternalURL(t *testing.T) {
	post := Post{1, "title1", "url1", "http://placehold.it/350x150", 123}
	url := post.InternalURL()
	if url != "/post/1/title1" {
		t.Fail()
	}
}

func TestInternalURLLong(t *testing.T) {
	post := Post{1, strings.Repeat("a", 50), "url1", "http://placehold.it/350x150", 123}
	url := post.InternalURL()
	if url != "/post/1/"+strings.Repeat("a", 30) {
		t.Fail()
	}
}

func TestAddPost(t *testing.T) {
	post := Post{1, "title1", "url1", "http://placehold.it/350x150", 123}
	board := NewBoard([]Post{})
	board.AddPost(post)
	if len(board.Posts) != 1 {
		t.Fail()
	}
	if board.Posts[0].Title != "title1" {
		t.Fail()
	}
	board.AddPost(post)
	if len(board.Posts) != 1 {
		t.Fail()
	}
}

func TestPostsToJSON(t *testing.T) {
	posts := make([]Post, 2)
	posts[0] = Post{1, "title1", "url1", "http://placehold.it/350x150", 123}
	posts[1] = Post{2, "title2", "url2", "http://placehold.it/350x150", 124}
	board := NewBoard(posts)
	data := board.PostsToJSON()
	if len(*data) != 2 {
		t.Fail()
	}
	post := (*data)[0]
	if post.ID != 1 {
		t.Fail()
	}
	if post.Title != "title1" {
		t.Fail()
	}
	if post.URL != "url1" {
		t.Fail()
	}
	if post.Image != "http://placehold.it/350x150" {
		t.Fail()
	}
	if post.Likes != 123 {
		t.Fail()
	}
	if post.InternalURL != "/post/1/title1" {
		t.Fail()
	}
}

func TestFilterBoard(t *testing.T) {
	posts := make([]Post, 2)
	posts[0] = Post{1, "title1", "url1", "http://placehold.it/350x150", 123}
	posts[1] = Post{2, "title2", "url2", "http://placehold.it/350x150", 124}
	board := NewBoard(posts)
	newBoard := board.FilterBoard("title2")
	if len(newBoard.Posts) != 1 {
		t.Fail()
	}
	if newBoard.Posts[0].ID != 2 {
		t.Fail()
	}
}

func TestLimitBoard(t *testing.T) {
	posts := make([]Post, 2)
	posts[0] = Post{1, "title1", "url1", "http://placehold.it/350x150", 123}
	posts[1] = Post{2, "title2", "url2", "http://placehold.it/350x150", 124}
	board := NewBoard(posts)
	board.LimitBoard(1, 1)
	if len(board.Posts) != 1 {
		t.Fail()
	}
	if board.Posts[0].ID != 2 {
		t.Fail()
	}
	board.LimitBoard(1, 3)
	if len(board.Posts) != 0 {
		t.Fail()
	}
	board.LimitBoard(10, 1)
	if len(board.Posts) != 0 {
		t.Fail()
	}
}

func TestSortPostsByLikes(t *testing.T) {
	board := NewBoard([]Post{})
	board.AddPost(Post{3, "title3", "url3", "http://placehold.it/350x150", 123})
	board.AddPost(Post{1, "title1", "url1", "http://placehold.it/350x150", 121})
	board.AddPost(Post{2, "title2", "url2", "http://placehold.it/350x150", 122})
	board.SortPostsByLikes()
	if board.Posts[0].Likes != 123 {
		t.Fail()
	}
	if board.Posts[1].Likes != 122 {
		t.Fail()
	}
	if board.Posts[2].Likes != 121 {
		t.Fail()
	}
}

func TestReset(t *testing.T) {
	board := NewBoard([]Post{})
	board.AddPost(Post{3, "title3", "url3", "http://placehold.it/350x150", 123})
	if len(board.Posts) != 1 {
		t.Fail()
	}
	board.Reset()
	if len(board.Posts) != 0 {
		t.Fail()
	}
}

func TestURLs(t *testing.T) {
	board := NewBoard([]Post{})
	board.AddPost(Post{3, "title3", "url3", "http://placehold.it/350x150", 123})
	board.AddPost(Post{1, "title1", "url1", "http://placehold.it/350x150", 121})
	board.AddPost(Post{2, "title2", "url2", "http://placehold.it/350x150", 122})
	urls := board.URLs()
	if len(urls) != 3 {
		t.Fail()
	}
	if urls[0] != "/post/3/title3" {
		t.Fail()
	}
	if urls[1] != "/post/1/title1" {
		t.Fail()
	}
	if urls[2] != "/post/2/title2" {
		t.Fail()
	}
}

func TestKeywords(t *testing.T) {
	board := NewBoard([]Post{})
	board.AddPost(Post{3, "title2", "url3", "http://placehold.it/350x150", 123})
	board.AddPost(Post{1, "title1", "url1", "http://placehold.it/350x150", 121})
	board.AddPost(Post{2, "title1 title2 title2", "url2", "http://placehold.it/350x150", 122})
	keywords := board.Keywords()
	if len(keywords) != 2 {
		t.Fail()
	}
	if keywords[0] != "title2" {
		t.Fail()
	}
	if keywords[1] != "title1" {
		t.Fail()
	}
}

func TestKeywordsLong(t *testing.T) {
	board := NewBoard([]Post{})
	title := []string{}
	for x := 10000; x < 10100; x++ {
		title = append(title, strconv.FormatInt(int64(x), 10))
	}
	board.AddPost(Post{1, strings.Join(title, " "), "url1", "http://placehold.it/350x150", 121})
	keywords := board.Keywords()
	if len(keywords) != MaxKeywords {
		t.Fail()
	}
}

func TestGetImageFromPostBody(t *testing.T) {
	body := ""
	image := getImageFromPostBody(body)
	if image != "img.gif" {
		t.Fail()
	}
}

func TestGetImageFromPostBodyNotFound(t *testing.T) {
	body := "

"
	image := getImageFromPostBody(body)
	if image != "" {
		t.Fail()
	}
}
<commit_msg>Switch post_test to testify<commit_after>package tumblr

import (
	"strconv"
	"strings"
	"testing"

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

func TestPost(t *testing.T) {
	post := Post{
		1234,
		"title",
		"url",
		"http://placehold.it/350x150",
		123,
	}
	assert.Equal(t, post.ID, int64(1234))
	assert.Equal(t, post.Title, "title")
	assert.Equal(t, post.URL, "url")
	assert.Equal(t, post.Image, "http://placehold.it/350x150")
	assert.Equal(t, post.Likes, int64(123))
}

func TestCSVToPost(t *testing.T) {
	row := make([]string, 5)
	row[0] = "1234"
	row[1] = "title"
	row[2] = "url"
	row[3] = "http://placehold.it/350x150"
	row[4] = "123"
	post := CSVToPost(row)
	assert.Equal(t, post.ID, int64(1234))
	assert.Equal(t, post.Title, "title")
	assert.Equal(t, post.URL, "url")
	assert.Equal(t, post.Image, "http://placehold.it/350x150")
	assert.Equal(t, post.Likes, int64(123))
}

func TestCorruptPost(t *testing.T) {
	row := make([]string, 5)
	post := CSVToPost(row)
	assert.Equal(t, post.ID, int64(0))
}

func TestInternalURL(t *testing.T) {
	post := Post{1, "title1", "url1", "http://placehold.it/350x150", 123}
	url := post.InternalURL()
	assert.Equal(t, url, "/post/1/title1")
}

func TestInternalURLLong(t *testing.T) {
	post := Post{1, strings.Repeat("a", 50), "url1", "http://placehold.it/350x150", 123}
	url := post.InternalURL()
	assert.Equal(t, url, "/post/1/"+strings.Repeat("a", 30))
}

func TestAddPost(t *testing.T) {
	post := Post{1, "title1", "url1", "http://placehold.it/350x150", 123}
	board := NewBoard([]Post{})
	board.AddPost(post)
	assert.Equal(t, len(board.Posts), 1)
	assert.Equal(t, board.Posts[0].Title, "title1")

	board.AddPost(post)
	assert.Equal(t, len(board.Posts), 1)
}

func TestPostsToJSON(t *testing.T) {
	posts := make([]Post, 2)
	posts[0] = Post{1, "title1", "url1", "http://placehold.it/350x150", 123}
	posts[1] = Post{2, "title2", "url2", "http://placehold.it/350x150", 124}
	board := NewBoard(posts)
	data := board.PostsToJSON()
	assert.Equal(t, len(*data), 2)

	post := (*data)[0]
	assert.Equal(t, post.ID, int64(1))
	assert.Equal(t, post.Title, "title1")
	assert.Equal(t, post.URL, "url1")
	assert.Equal(t, post.Image, "http://placehold.it/350x150")
	assert.Equal(t, post.Likes, int64(123))
	assert.Equal(t, post.InternalURL, "/post/1/title1")
}

func TestFilterBoard(t *testing.T) {
	posts := make([]Post, 2)
	posts[0] = Post{1, "title1", "url1", "http://placehold.it/350x150", 123}
	posts[1] = Post{2, "title2", "url2", "http://placehold.it/350x150", 124}
	board := NewBoard(posts)
	newBoard := board.FilterBoard("title2")
	assert.Equal(t, len(newBoard.Posts), 1)
	assert.Equal(t, newBoard.Posts[0].ID, int64(2))
}

func TestLimitBoard(t *testing.T) {
	posts := make([]Post, 2)
	posts[0] = Post{1, "title1", "url1", "http://placehold.it/350x150", 123}
	posts[1] = Post{2, "title2", "url2", "http://placehold.it/350x150", 124}
	board := NewBoard(posts)
	board.LimitBoard(1, 1)
	assert.Equal(t, len(board.Posts), 1)
	assert.Equal(t, board.Posts[0].ID, int64(2))

	board.LimitBoard(1, 3)
	assert.Equal(t, len(board.Posts), 0)

	board.LimitBoard(10, 1)
	assert.Equal(t, len(board.Posts), 0)
}

func TestSortPostsByLikes(t *testing.T) {
	board := NewBoard([]Post{})
	board.AddPost(Post{3, "title3", "url3", "http://placehold.it/350x150", 123})
	board.AddPost(Post{1, "title1", "url1", "http://placehold.it/350x150", 121})
	board.AddPost(Post{2, "title2", "url2", "http://placehold.it/350x150", 122})
	board.SortPostsByLikes()
	assert.Equal(t, board.Posts[0].Likes, int64(123))
	assert.Equal(t, board.Posts[1].Likes, int64(122))
	assert.Equal(t, board.Posts[2].Likes, int64(121))
}

func TestReset(t *testing.T) {
	board := NewBoard([]Post{})
	board.AddPost(Post{3, "title3", "url3", "http://placehold.it/350x150", 123})
	assert.Equal(t, len(board.Posts), 1)

	board.Reset()
	assert.Equal(t, len(board.Posts), 0)
}

func TestURLs(t *testing.T) {
	board := NewBoard([]Post{})
	board.AddPost(Post{3, "title3", "url3", "http://placehold.it/350x150", 123})
	board.AddPost(Post{1, "title1", "url1", "http://placehold.it/350x150", 121})
	board.AddPost(Post{2, "title2", "url2", "http://placehold.it/350x150", 122})
	urls := board.URLs()
	assert.Equal(t, len(urls), 3)
	assert.Equal(t, urls[0], "/post/3/title3")
	assert.Equal(t, urls[1], "/post/1/title1")
	assert.Equal(t, urls[2], "/post/2/title2")
}

func TestKeywords(t *testing.T) {
	board := NewBoard([]Post{})
	board.AddPost(Post{3, "title2", "url3", "http://placehold.it/350x150", 123})
	board.AddPost(Post{1, "title1", "url1", "http://placehold.it/350x150", 121})
	board.AddPost(Post{2, "title1 title2 title2", "url2", "http://placehold.it/350x150", 122})
	keywords := board.Keywords()
	assert.Equal(t, len(keywords), 2)
	assert.Equal(t, keywords[0], "title2")
	assert.Equal(t, keywords[1], "title1")
}

func TestKeywordsLong(t *testing.T) {
	board := NewBoard([]Post{})
	title := []string{}
	for x := 10000; x < 10100; x++ {
		title = append(title, strconv.FormatInt(int64(x), 10))
	}
	board.AddPost(Post{1, strings.Join(title, " "), "url1", "http://placehold.it/350x150", 121})
	keywords := board.Keywords()
	assert.Equal(t, len(keywords), MaxKeywords)
}

func TestGetImageFromPostBody(t *testing.T) {
	body := ""
	image := getImageFromPostBody(body)
	assert.Equal(t, image, "img.gif")
}

func TestGetImageFromPostBodyNotFound(t *testing.T) {
	body := "

"
	image := getImageFromPostBody(body)
	assert.Equal(t, image, "")
}
<|endoftext|>


<commit_before>package file

import (
	"flag"
	"os"
	"path/filepath"
	"time"

	"github.com/bmatcuk/doublestar"
	"github.com/go-kit/log"
	"github.com/go-kit/log/level"
	"github.com/pkg/errors"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/common/model"
	fsnotify "gopkg.in/fsnotify.v1"

	"github.com/grafana/loki/clients/pkg/promtail/api"
	"github.com/grafana/loki/clients/pkg/promtail/client"
	"github.com/grafana/loki/clients/pkg/promtail/positions"
	"github.com/grafana/loki/clients/pkg/promtail/targets/target"
)

const (
	FilenameLabel = "filename"
)

// Config describes behavior for Target
type Config struct {
	SyncPeriod time.Duration `yaml:"sync_period"`
	Stdin      bool          `yaml:"stdin"`
}

// RegisterFlags with prefix registers flags where every name is prefixed by
// prefix. If prefix is a non-empty string, prefix should end with a period.
func (cfg *Config) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) {
	f.DurationVar(&cfg.SyncPeriod, prefix+"target.sync-period", 10*time.Second, "Period to resync directories being watched and files being tailed.")
	f.BoolVar(&cfg.Stdin, prefix+"stdin", false, "Set to true to pipe logs to promtail.")
}

// RegisterFlags register flags.
func (cfg *Config) RegisterFlags(flags *flag.FlagSet) {
	cfg.RegisterFlagsWithPrefix("", flags)
}

type fileTargetEventType string

const (
	fileTargetEventWatchStart fileTargetEventType = "WATCH_START"
	fileTargetEventWatchStop  fileTargetEventType = "WATCH_STOP"
)

type fileTargetEvent struct {
	path      string
	eventType fileTargetEventType
}

// FileTarget describes a particular set of logs.
// nolint:revive
type FileTarget struct {
	metrics *Metrics
	logger  log.Logger

	handler          api.EntryHandler
	positions        positions.Positions
	labels           model.LabelSet
	discoveredLabels model.LabelSet

	fileEventWatcher   chan fsnotify.Event
	targetEventHandler chan fileTargetEvent
	watches            map[string]struct{}
	path               string
	pathExclude        string
	quit               chan struct{}
	done               chan struct{}

	tails map[string]*tailer

	targetConfig *Config
}

// NewFileTarget create a new FileTarget.
func NewFileTarget(
	metrics *Metrics,
	logger log.Logger,
	handler api.EntryHandler,
	positions positions.Positions,
	path string,
	pathExclude string,
	labels model.LabelSet,
	discoveredLabels model.LabelSet,
	targetConfig *Config,
	fileEventWatcher chan fsnotify.Event,
	targetEventHandler chan fileTargetEvent,
) (*FileTarget, error) {
	t := &FileTarget{
		logger:             logger,
		metrics:            metrics,
		path:               path,
		pathExclude:        pathExclude,
		labels:             labels,
		discoveredLabels:   discoveredLabels,
		handler:            api.AddLabelsMiddleware(labels).Wrap(handler),
		positions:          positions,
		quit:               make(chan struct{}),
		done:               make(chan struct{}),
		tails:              map[string]*tailer{},
		targetConfig:       targetConfig,
		fileEventWatcher:   fileEventWatcher,
		targetEventHandler: targetEventHandler,
	}

	go t.run()
	return t, nil
}

// Ready if at least one file is being tailed
func (t *FileTarget) Ready() bool {
	return len(t.tails) > 0
}

// Stop the target.
func (t *FileTarget) Stop() {
	close(t.quit)
	<-t.done
	t.handler.Stop()
}

// Type implements a Target
func (t *FileTarget) Type() target.TargetType {
	return target.FileTargetType
}

// DiscoveredLabels implements a Target
func (t *FileTarget) DiscoveredLabels() model.LabelSet {
	return t.discoveredLabels
}

// Labels implements a Target
func (t *FileTarget) Labels() model.LabelSet {
	return t.labels
}

// Details implements a Target
func (t *FileTarget) Details() interface{} {
	files := map[string]int64{}
	for fileName := range t.tails {
		files[fileName], _ = t.positions.Get(fileName)
	}
	return files
}

func (t *FileTarget) run() {
	defer func() {
		for _, v := range t.tails {
			v.stop()
		}
		level.Info(t.logger).Log("msg", "filetarget: watcher closed, tailer stopped, positions saved", "path", t.path)
		close(t.done)
	}()

	err := t.sync()
	if err != nil {
		level.Error(t.logger).Log("msg", "error running sync function", "error", err)
	}

	ticker := time.NewTicker(t.targetConfig.SyncPeriod)
	defer ticker.Stop()

	for {
		select {
		case event := <-t.fileEventWatcher:
			switch event.Op {
			case fsnotify.Create:
				t.startTailing([]string{event.Name})
			default:
				// No-op we only care about Create events
			}
		case <-ticker.C:
			err := t.sync()
			if err != nil {
				level.Error(t.logger).Log("msg", "error running sync function", "error", err)
			}
		case <-t.quit:
			return
		}
	}
}

func (t *FileTarget) sync() error {
	var matches, matchesExcluded []string
	if fi, err := os.Stat(t.path); err == nil && !fi.IsDir() {
		// if the path points to a file that exists, then it we can skip the Glob search
		matches = []string{t.path}
	} else {
		// Gets current list of files to tail.
		matches, err = doublestar.Glob(t.path)
		if err != nil {
			return errors.Wrap(err, "filetarget.sync.filepath.Glob")
		}
	}

	if fi, err := os.Stat(t.pathExclude); err == nil && !fi.IsDir() {
		matchesExcluded = []string{t.pathExclude}
	} else {
		matchesExcluded, err = doublestar.Glob(t.pathExclude)
		if err != nil {
			return errors.Wrap(err, "filetarget.sync.filepathexclude.Glob")
		}
	}

	for i := 0; i < len(matchesExcluded); i++ {
		for j := 0; j < len(matches); j++ {
			if matchesExcluded[i] == matches[j] {
				// exclude this specific match
				matches = append(matches[:j], matches[j+1:]...)
			}
		}
	}

	if len(matches) == 0 {
		level.Debug(t.logger).Log("msg", "no files matched requested path, nothing will be tailed", "path", t.path, "pathExclude", t.pathExclude)
	}

	// Gets absolute path for each pattern.
	for i := 0; i < len(matches); i++ {
		if !filepath.IsAbs(matches[i]) {
			path, err := filepath.Abs(matches[i])
			if err != nil {
				return errors.Wrap(err, "filetarget.sync.filepath.Abs")
			}
			matches[i] = path
		}
	}

	// Record the size of all the files matched by the Glob pattern.
	t.reportSize(matches)

	// Get the current unique set of dirs to watch.
	dirs := map[string]struct{}{}
	for _, p := range matches {
		dirs[filepath.Dir(p)] = struct{}{}
	}

	// Add any directories which are not already being watched.
	toStartWatching := missing(t.watches, dirs)
	t.startWatching(toStartWatching)

	// Remove any directories which no longer need watching.
	toStopWatching := missing(dirs, t.watches)
	t.stopWatching(toStopWatching)

	// fsnotify.Watcher doesn't allow us to see what is currently being watched so we have to track it ourselves.
	t.watches = dirs

	// Check if any running tailers have stopped because of errors and remove them from the running list
	// (They will be restarted in startTailing)
	t.pruneStoppedTailers()

	// Start tailing all of the matched files if not already doing so.
	t.startTailing(matches)

	// Stop tailing any files which no longer exist
	toStopTailing := toStopTailing(matches, t.tails)
	t.stopTailingAndRemovePosition(toStopTailing)

	return nil
}

func (t *FileTarget) startWatching(dirs map[string]struct{}) {
	for dir := range dirs {
		if _, ok := t.watches[dir]; ok {
			continue
		}
		level.Info(t.logger).Log("msg", "watching new directory", "directory", dir)
		t.targetEventHandler <- fileTargetEvent{
			path:      dir,
			eventType: fileTargetEventWatchStart,
		}
	}
}

func (t *FileTarget) stopWatching(dirs map[string]struct{}) {
	for dir := range dirs {
		if _, ok := t.watches[dir]; !ok {
			continue
		}
		level.Info(t.logger).Log("msg", "removing directory from watcher", "directory", dir)
		t.targetEventHandler <- fileTargetEvent{
			path:      dir,
			eventType: fileTargetEventWatchStop,
		}
	}
}

func (t *FileTarget) startTailing(ps []string) {
	for _, p := range ps {
		if _, ok := t.tails[p]; ok {
			continue
		}

		fi, err := os.Stat(p)
		if err != nil {
			level.Error(t.logger).Log("msg", "failed to tail file, stat failed", "error", err, "filename", p)
			t.metrics.totalBytes.DeleteLabelValues(p)
			continue
		}

		if fi.IsDir() {
			level.Info(t.logger).Log("msg", "failed to tail file", "error", "file is a directory", "filename", p)
			t.metrics.totalBytes.DeleteLabelValues(p)
			continue
		}

		level.Debug(t.logger).Log("msg", "tailing new file", "filename", p)
		tailer, err := newTailer(t.metrics, t.logger, t.handler, t.positions, p)
		if err != nil {
			level.Error(t.logger).Log("msg", "failed to start tailer", "error", err, "filename", p)
			continue
		}
		t.tails[p] = tailer
	}
}

// stopTailingAndRemovePosition will stop the tailer and remove the positions entry.
// Call this when a file no longer exists and you want to remove all traces of it.
func (t *FileTarget) stopTailingAndRemovePosition(ps []string) {
	for _, p := range ps {
		if tailer, ok := t.tails[p]; ok {
			tailer.stop()
			t.positions.Remove(tailer.path)
			delete(t.tails, p)
		}
		if h, ok := t.handler.(api.InstrumentedEntryHandler); ok {
			h.UnregisterLatencyMetric(prometheus.Labels{client.LatencyLabel: p})
		}
	}
}

// pruneStoppedTailers removes any tailers which have stopped running from
// the list of active tailers. This allows them to be restarted if there were errors.
func (t *FileTarget) pruneStoppedTailers() {
	toRemove := make([]string, 0, len(t.tails))
	for k, t := range t.tails {
		if !t.isRunning() {
			toRemove = append(toRemove, k)
		}
	}
	for _, tr := range toRemove {
		delete(t.tails, tr)
	}
}

func toStopTailing(nt []string, et map[string]*tailer) []string {
	// Make a set of all existing tails
	existingTails := make(map[string]struct{}, len(et))
	for file := range et {
		existingTails[file] = struct{}{}
	}
	// Make a set of what we are about to start tailing
	newTails := make(map[string]struct{}, len(nt))
	for _, p := range nt {
		newTails[p] = struct{}{}
	}
	// Find the tails in our existing which are not in the new, these need to be stopped!
	ts := missing(newTails, existingTails)
	ta := make([]string, len(ts))
	i := 0
	for t := range ts {
		ta[i] = t
		i++
	}
	return ta
}

func (t *FileTarget) reportSize(ms []string) {
	for _, m := range ms {
		// Ask the tailer to update the size if a tailer exists, this keeps position and size metrics in sync
		if tailer, ok := t.tails[m]; ok {
			err := tailer.markPositionAndSize()
			if err != nil {
				level.Warn(t.logger).Log("msg", "failed to get file size from tailer, ", "file", m, "error", err)
				return
			}
		} else {
			// Must be a new file, just directly read the size of it
			fi, err := os.Stat(m)
			if err != nil {
				// If the file was deleted between when the glob match and here,
				// we just ignore recording a size for it,
				// the tail code will also check if the file exists before creating a tailer.
				return
			}
			t.metrics.totalBytes.WithLabelValues(m).Set(float64(fi.Size()))
		}

	}
}

// Returns the elements from set b which are missing from set a
func missing(as map[string]struct{}, bs map[string]struct{}) map[string]struct{} {
	c := map[string]struct{}{}
	for a := range bs {
		if _, ok := as[a]; !ok {
			c[a] = struct{}{}
		}
	}
	return c
}
<commit_msg>promtail: fix infinite loop in file target (fixes #5350) (#6135)<commit_after>package file

import (
	"flag"
	"os"
	"path/filepath"
	"time"

	"github.com/bmatcuk/doublestar"
	"github.com/go-kit/log"
	"github.com/go-kit/log/level"
	"github.com/pkg/errors"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/common/model"
	fsnotify "gopkg.in/fsnotify.v1"

	"github.com/grafana/loki/clients/pkg/promtail/api"
	"github.com/grafana/loki/clients/pkg/promtail/client"
	"github.com/grafana/loki/clients/pkg/promtail/positions"
	"github.com/grafana/loki/clients/pkg/promtail/targets/target"
)

const (
	FilenameLabel = "filename"
)

// Config describes behavior for Target
type Config struct {
	SyncPeriod time.Duration `yaml:"sync_period"`
	Stdin      bool          `yaml:"stdin"`
}

// RegisterFlags with prefix registers flags where every name is prefixed by
// prefix. If prefix is a non-empty string, prefix should end with a period.
func (cfg *Config) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) {
	f.DurationVar(&cfg.SyncPeriod, prefix+"target.sync-period", 10*time.Second, "Period to resync directories being watched and files being tailed.")
	f.BoolVar(&cfg.Stdin, prefix+"stdin", false, "Set to true to pipe logs to promtail.")
}

// RegisterFlags register flags.
func (cfg *Config) RegisterFlags(flags *flag.FlagSet) {
	cfg.RegisterFlagsWithPrefix("", flags)
}

type fileTargetEventType string

const (
	fileTargetEventWatchStart fileTargetEventType = "WATCH_START"
	fileTargetEventWatchStop  fileTargetEventType = "WATCH_STOP"
)

type fileTargetEvent struct {
	path      string
	eventType fileTargetEventType
}

// FileTarget describes a particular set of logs.
// nolint:revive
type FileTarget struct {
	metrics *Metrics
	logger  log.Logger

	handler          api.EntryHandler
	positions        positions.Positions
	labels           model.LabelSet
	discoveredLabels model.LabelSet

	fileEventWatcher   chan fsnotify.Event
	targetEventHandler chan fileTargetEvent
	watches            map[string]struct{}
	path               string
	pathExclude        string
	quit               chan struct{}
	done               chan struct{}

	tails map[string]*tailer

	targetConfig *Config
}

// NewFileTarget create a new FileTarget.
func NewFileTarget(
	metrics *Metrics,
	logger log.Logger,
	handler api.EntryHandler,
	positions positions.Positions,
	path string,
	pathExclude string,
	labels model.LabelSet,
	discoveredLabels model.LabelSet,
	targetConfig *Config,
	fileEventWatcher chan fsnotify.Event,
	targetEventHandler chan fileTargetEvent,
) (*FileTarget, error) {
	t := &FileTarget{
		logger:             logger,
		metrics:            metrics,
		path:               path,
		pathExclude:        pathExclude,
		labels:             labels,
		discoveredLabels:   discoveredLabels,
		handler:            api.AddLabelsMiddleware(labels).Wrap(handler),
		positions:          positions,
		quit:               make(chan struct{}),
		done:               make(chan struct{}),
		tails:              map[string]*tailer{},
		targetConfig:       targetConfig,
		fileEventWatcher:   fileEventWatcher,
		targetEventHandler: targetEventHandler,
	}

	go t.run()
	return t, nil
}

// Ready if at least one file is being tailed
func (t *FileTarget) Ready() bool {
	return len(t.tails) > 0
}

// Stop the target.
func (t *FileTarget) Stop() {
	close(t.quit)
	<-t.done
	t.handler.Stop()
}

// Type implements a Target
func (t *FileTarget) Type() target.TargetType {
	return target.FileTargetType
}

// DiscoveredLabels implements a Target
func (t *FileTarget) DiscoveredLabels() model.LabelSet {
	return t.discoveredLabels
}

// Labels implements a Target
func (t *FileTarget) Labels() model.LabelSet {
	return t.labels
}

// Details implements a Target
func (t *FileTarget) Details() interface{} {
	files := map[string]int64{}
	for fileName := range t.tails {
		files[fileName], _ = t.positions.Get(fileName)
	}
	return files
}

func (t *FileTarget) run() {
	defer func() {
		for _, v := range t.tails {
			v.stop()
		}
		level.Info(t.logger).Log("msg", "filetarget: watcher closed, tailer stopped, positions saved", "path", t.path)
		close(t.done)
	}()

	err := t.sync()
	if err != nil {
		level.Error(t.logger).Log("msg", "error running sync function", "error", err)
	}

	ticker := time.NewTicker(t.targetConfig.SyncPeriod)
	defer ticker.Stop()

	for {
		select {
		case event, ok := <-t.fileEventWatcher:
			if !ok {
				// fileEventWatcher has been closed
				return
			}
			switch event.Op {
			case fsnotify.Create:
				t.startTailing([]string{event.Name})
			default:
				// No-op we only care about Create events
			}
		case <-ticker.C:
			err := t.sync()
			if err != nil {
				level.Error(t.logger).Log("msg", "error running sync function", "error", err)
			}
		case <-t.quit:
			return
		}
	}
}

func (t *FileTarget) sync() error {
	var matches, matchesExcluded []string
	if fi, err := os.Stat(t.path); err == nil && !fi.IsDir() {
		// if the path points to a file that exists, then it we can skip the Glob search
		matches = []string{t.path}
	} else {
		// Gets current list of files to tail.
		matches, err = doublestar.Glob(t.path)
		if err != nil {
			return errors.Wrap(err, "filetarget.sync.filepath.Glob")
		}
	}

	if fi, err := os.Stat(t.pathExclude); err == nil && !fi.IsDir() {
		matchesExcluded = []string{t.pathExclude}
	} else {
		matchesExcluded, err = doublestar.Glob(t.pathExclude)
		if err != nil {
			return errors.Wrap(err, "filetarget.sync.filepathexclude.Glob")
		}
	}

	for i := 0; i < len(matchesExcluded); i++ {
		for j := 0; j < len(matches); j++ {
			if matchesExcluded[i] == matches[j] {
				// exclude this specific match
				matches = append(matches[:j], matches[j+1:]...)
			}
		}
	}

	if len(matches) == 0 {
		level.Debug(t.logger).Log("msg", "no files matched requested path, nothing will be tailed", "path", t.path, "pathExclude", t.pathExclude)
	}

	// Gets absolute path for each pattern.
	for i := 0; i < len(matches); i++ {
		if !filepath.IsAbs(matches[i]) {
			path, err := filepath.Abs(matches[i])
			if err != nil {
				return errors.Wrap(err, "filetarget.sync.filepath.Abs")
			}
			matches[i] = path
		}
	}

	// Record the size of all the files matched by the Glob pattern.
	t.reportSize(matches)

	// Get the current unique set of dirs to watch.
	dirs := map[string]struct{}{}
	for _, p := range matches {
		dirs[filepath.Dir(p)] = struct{}{}
	}

	// Add any directories which are not already being watched.
	toStartWatching := missing(t.watches, dirs)
	t.startWatching(toStartWatching)

	// Remove any directories which no longer need watching.
	toStopWatching := missing(dirs, t.watches)
	t.stopWatching(toStopWatching)

	// fsnotify.Watcher doesn't allow us to see what is currently being watched so we have to track it ourselves.
	t.watches = dirs

	// Check if any running tailers have stopped because of errors and remove them from the running list
	// (They will be restarted in startTailing)
	t.pruneStoppedTailers()

	// Start tailing all of the matched files if not already doing so.
	t.startTailing(matches)

	// Stop tailing any files which no longer exist
	toStopTailing := toStopTailing(matches, t.tails)
	t.stopTailingAndRemovePosition(toStopTailing)

	return nil
}

func (t *FileTarget) startWatching(dirs map[string]struct{}) {
	for dir := range dirs {
		if _, ok := t.watches[dir]; ok {
			continue
		}
		level.Info(t.logger).Log("msg", "watching new directory", "directory", dir)
		t.targetEventHandler <- fileTargetEvent{
			path:      dir,
			eventType: fileTargetEventWatchStart,
		}
	}
}

func (t *FileTarget) stopWatching(dirs map[string]struct{}) {
	for dir := range dirs {
		if _, ok := t.watches[dir]; !ok {
			continue
		}
		level.Info(t.logger).Log("msg", "removing directory from watcher", "directory", dir)
		t.targetEventHandler <- fileTargetEvent{
			path:      dir,
			eventType: fileTargetEventWatchStop,
		}
	}
}

func (t *FileTarget) startTailing(ps []string) {
	for _, p := range ps {
		if _, ok := t.tails[p]; ok {
			continue
		}

		fi, err := os.Stat(p)
		if err != nil {
			level.Error(t.logger).Log("msg", "failed to tail file, stat failed", "error", err, "filename", p)
			t.metrics.totalBytes.DeleteLabelValues(p)
			continue
		}

		if fi.IsDir() {
			level.Info(t.logger).Log("msg", "failed to tail file", "error", "file is a directory", "filename", p)
			t.metrics.totalBytes.DeleteLabelValues(p)
			continue
		}

		level.Debug(t.logger).Log("msg", "tailing new file", "filename", p)
		tailer, err := newTailer(t.metrics, t.logger, t.handler, t.positions, p)
		if err != nil {
			level.Error(t.logger).Log("msg", "failed to start tailer", "error", err, "filename", p)
			continue
		}
		t.tails[p] = tailer
	}
}

// stopTailingAndRemovePosition will stop the tailer and remove the positions entry.
// Call this when a file no longer exists and you want to remove all traces of it.
func (t *FileTarget) stopTailingAndRemovePosition(ps []string) {
	for _, p := range ps {
		if tailer, ok := t.tails[p]; ok {
			tailer.stop()
			t.positions.Remove(tailer.path)
			delete(t.tails, p)
		}
		if h, ok := t.handler.(api.InstrumentedEntryHandler); ok {
			h.UnregisterLatencyMetric(prometheus.Labels{client.LatencyLabel: p})
		}
	}
}

// pruneStoppedTailers removes any tailers which have stopped running from
// the list of active tailers. This allows them to be restarted if there were errors.
func (t *FileTarget) pruneStoppedTailers() {
	toRemove := make([]string, 0, len(t.tails))
	for k, t := range t.tails {
		if !t.isRunning() {
			toRemove = append(toRemove, k)
		}
	}
	for _, tr := range toRemove {
		delete(t.tails, tr)
	}
}

func toStopTailing(nt []string, et map[string]*tailer) []string {
	// Make a set of all existing tails
	existingTails := make(map[string]struct{}, len(et))
	for file := range et {
		existingTails[file] = struct{}{}
	}
	// Make a set of what we are about to start tailing
	newTails := make(map[string]struct{}, len(nt))
	for _, p := range nt {
		newTails[p] = struct{}{}
	}
	// Find the tails in our existing which are not in the new, these need to be stopped!
	ts := missing(newTails, existingTails)
	ta := make([]string, len(ts))
	i := 0
	for t := range ts {
		ta[i] = t
		i++
	}
	return ta
}

func (t *FileTarget) reportSize(ms []string) {
	for _, m := range ms {
		// Ask the tailer to update the size if a tailer exists, this keeps position and size metrics in sync
		if tailer, ok := t.tails[m]; ok {
			err := tailer.markPositionAndSize()
			if err != nil {
				level.Warn(t.logger).Log("msg", "failed to get file size from tailer, ", "file", m, "error", err)
				return
			}
		} else {
			// Must be a new file, just directly read the size of it
			fi, err := os.Stat(m)
			if err != nil {
				// If the file was deleted between when the glob match and here,
				// we just ignore recording a size for it,
				// the tail code will also check if the file exists before creating a tailer.
				return
			}
			t.metrics.totalBytes.WithLabelValues(m).Set(float64(fi.Size()))
		}

	}
}

// Returns the elements from set b which are missing from set a
func missing(as map[string]struct{}, bs map[string]struct{}) map[string]struct{} {
	c := map[string]struct{}{}
	for a := range bs {
		if _, ok := as[a]; !ok {
			c[a] = struct{}{}
		}
	}
	return c
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Cisco and/or its affiliates.
//
// 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 logrus

import (
	"fmt"

	"github.com/Sirupsen/logrus"
	"github.com/ligato/cn-infra/logging"
	"sync"
	"sync/atomic"
)

// NewLogRegistry is a constructor
func NewLogRegistry() logging.Registry {
	registry := &logRegistry{}
	// Init mapping in loggers
	registry.loggers.Store(make(map[string]*Logger))
	// Put default logger loggers
	registry.putLoggerToMapping(defaultLogger)
	return registry
}

// logRegistry contains logger map and rwlock guarding access to it
type logRegistry struct {
	access sync.RWMutex
	// loggers holds mapping of logger instances indexed by their names
	loggers atomic.Value
}

// NewLogger creates new named Logger instance. Name can be subsequently used to
// refer the logger in registry.
func (lr *logRegistry) NewLogger(name string) logging.Logger {
	existingLogger := lr.getLoggerFromMapping(name)
	if existingLogger != nil {
		panic(fmt.Errorf("logger with name '%s' already exists", name))
	}
	if err := checkLoggerName(name); err != nil {
		panic(err)
	}

	logger := NewLogger(name)

	lr.putLoggerToMapping(logger)
	return logger
}

// ListLoggers returns a map (loggerName => log level)
func (lr *logRegistry) ListLoggers() map[string]string {
	mapping := lr.loggers.Load().(map[string]*Logger)
	list := map[string]string{}
	for k, v := range mapping {
		list[k] = v.GetLevel().String()
	}
	return list
}

// SetLevel modifies log level of selected logger in the registry
func (lr *logRegistry) SetLevel(logger, level string) error {
	lvl, err := logrus.ParseLevel(level)
	if err != nil {
		return err
	}
	logVal := lr.getLoggerFromMapping(logger)
	if logVal == nil {
		return fmt.Errorf("logger %v not found", logger)
	}
	if err == nil {
		switch lvl {
		case logrus.DebugLevel:
			logVal.SetLevel(logging.DebugLevel)
		case logrus.InfoLevel:
			logVal.SetLevel(logging.InfoLevel)
		case logrus.WarnLevel:
			logVal.SetLevel(logging.WarnLevel)
		case logrus.ErrorLevel:
			logVal.SetLevel(logging.ErrorLevel)
		case logrus.PanicLevel:
			logVal.SetLevel(logging.PanicLevel)
		case logrus.FatalLevel:
			logVal.SetLevel(logging.FatalLevel)
		}
	}

	return nil
}

// GetLevel returns the currently set log level of the logger
func (lr *logRegistry) GetLevel(logger string) (string, error) {
	logVal := lr.getLoggerFromMapping(logger)
	if logVal == nil {
		return "", fmt.Errorf("logger %s not found", logger)
	}
	return logVal.GetLevel().String(), nil
}

// Lookup returns a logger instance identified by name from registry
func (lr *logRegistry) Lookup(loggerName string) (logger logging.Logger, found bool) {
	mapping := lr.loggers.Load().(map[string]*Logger)
	logger, found = mapping[loggerName]
	return
}

// ClearRegistry removes all loggers except the default one from registry
func (lr *logRegistry) ClearRegistry() {
	mapping := lr.loggers.Load().(map[string]*Logger)
	for k := range mapping {
		if k != DefaultLoggerName {
			delete(mapping, k)
		}
	}
	lr.loggers.Store(mapping)
}

// putLoggerToMapping writes logger into map of named loggers
func (lr *logRegistry) putLoggerToMapping(logger *Logger) {
	lr.access.RLock()
	defer lr.access.RUnlock()

	mapping := lr.loggers.Load().(map[string]*Logger)
	mapping[logger.name] = logger

	lr.loggers.Store(mapping)
}

// getLoggerFromMapping returns a logger by its name
func (lr *logRegistry) getLoggerFromMapping(logger string) *Logger {
	lr.access.RLock()
	defer lr.access.RUnlock()

	mapping := lr.loggers.Load().(map[string]*Logger)
	if mapping != nil {
		loggerVal, ok := mapping[logger]
		if ok {
			return loggerVal
		}
		return nil
	}
	return nil
}
<commit_msg>resolve comments #3<commit_after>// Copyright (c) 2017 Cisco and/or its affiliates.
//
// 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 logrus

import (
	"fmt"

	"github.com/Sirupsen/logrus"
	"github.com/ligato/cn-infra/logging"
	"sync"
)

// NewLogRegistry is a constructor
func NewLogRegistry() logging.Registry {
	registry := &logRegistry{}
	// Init mapping in loggers
	registry.loggers = make(map[string]*Logger)
	// Put default logger loggers
	registry.putLoggerToMapping(defaultLogger)
	return registry
}

// logRegistry contains logger map and rwlock guarding access to it
type logRegistry struct {
	access sync.RWMutex
	// loggers holds mapping of logger instances indexed by their names
	loggers map[string]*Logger
}

// NewLogger creates new named Logger instance. Name can be subsequently used to
// refer the logger in registry.
func (lr *logRegistry) NewLogger(name string) logging.Logger {
	existingLogger := lr.getLoggerFromMapping(name)
	if existingLogger != nil {
		panic(fmt.Errorf("logger with name '%s' already exists", name))
	}
	if err := checkLoggerName(name); err != nil {
		panic(err)
	}

	logger := NewLogger(name)

	lr.putLoggerToMapping(logger)
	return logger
}

// ListLoggers returns a map (loggerName => log level)
func (lr *logRegistry) ListLoggers() map[string]string {
	lr.access.RLock()
	defer lr.access.RUnlock()

	list := map[string]string{}
	for k, v := range lr.loggers {
		list[k] = v.GetLevel().String()
	}
	return list
}

// SetLevel modifies log level of selected logger in the registry
func (lr *logRegistry) SetLevel(logger, level string) error {
	lvl, err := logrus.ParseLevel(level)
	if err != nil {
		return err
	}
	logVal := lr.getLoggerFromMapping(logger)
	if logVal == nil {
		return fmt.Errorf("logger %v not found", logger)
	}
	if err == nil {
		switch lvl {
		case logrus.DebugLevel:
			logVal.SetLevel(logging.DebugLevel)
		case logrus.InfoLevel:
			logVal.SetLevel(logging.InfoLevel)
		case logrus.WarnLevel:
			logVal.SetLevel(logging.WarnLevel)
		case logrus.ErrorLevel:
			logVal.SetLevel(logging.ErrorLevel)
		case logrus.PanicLevel:
			logVal.SetLevel(logging.PanicLevel)
		case logrus.FatalLevel:
			logVal.SetLevel(logging.FatalLevel)
		}
	}

	return nil
}

// GetLevel returns the currently set log level of the logger
func (lr *logRegistry) GetLevel(logger string) (string, error) {
	logVal := lr.getLoggerFromMapping(logger)
	if logVal == nil {
		return "", fmt.Errorf("logger %s not found", logger)
	}
	return logVal.GetLevel().String(), nil
}

// Lookup returns a logger instance identified by name from registry
func (lr *logRegistry) Lookup(loggerName string) (logger logging.Logger, found bool) {
	lr.access.RLock()
	defer lr.access.RUnlock()

	logger, found = lr.loggers[loggerName]
	return
}

// ClearRegistry removes all loggers except the default one from registry
func (lr *logRegistry) ClearRegistry() {
	lr.access.RLock()
	defer lr.access.RUnlock()

	for k := range lr.loggers {
		if k != DefaultLoggerName {
			delete(lr.loggers, k)
		}
	}
}

// putLoggerToMapping writes logger into map of named loggers
func (lr *logRegistry) putLoggerToMapping(logger *Logger) {
	lr.access.RLock()
	defer lr.access.RUnlock()

	lr.loggers[logger.name] = logger
}

// getLoggerFromMapping returns a logger by its name
func (lr *logRegistry) getLoggerFromMapping(logger string) *Logger {
	lr.access.RLock()
	defer lr.access.RUnlock()

	if lr.loggers != nil {
		loggerVal, ok := lr.loggers[logger]
		if ok {
			return loggerVal
		}
		return nil
	}
	return nil
}
<|endoftext|>