<commit_before>package http_loader

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"sync"
	"time"

	"github.com/go-resty/resty/v2"
	"github.com/karimra/gnmic/actions"
	"github.com/karimra/gnmic/loaders"
	"github.com/karimra/gnmic/types"
	"github.com/karimra/gnmic/utils"
	"gopkg.in/yaml.v2"
)

const (
	loggingPrefix   = "[http_loader] "
	loaderType      = "http"
	defaultInterval = 1 * time.Minute
	defaultTimeout  = 50 * time.Second
)

func init() {
	loaders.Register(loaderType, func() loaders.TargetLoader {
		return &httpLoader{
			cfg:         &cfg{},
			lastTargets: make(map[string]*types.TargetConfig),
			logger:      log.New(io.Discard, loggingPrefix, utils.DefaultLoggingFlags),
		}
	})
}

type httpLoader struct {
	cfg            *cfg
	m              *sync.RWMutex
	lastTargets    map[string]*types.TargetConfig
	targetConfigFn func(*types.TargetConfig) error
	logger         *log.Logger
	//
	vars          map[string]interface{}
	actionsConfig map[string]map[string]interface{}
	addActions    []actions.Action
	delActions    []actions.Action
	numActions    int
}

type cfg struct {
	// the server URL, must include http or https as a prefix
	URL string `json:"url,omitempty" mapstructure:"url,omitempty"`
	// server query interval
	Interval time.Duration `json:"interval,omitempty" mapstructure:"interval,omitempty"`
	// query timeout
	Timeout time.Duration `json:"timeout,omitempty" mapstructure:"timeout,omitempty"`
	// TLS config
	SkipVerify bool   `json:"skip-verify,omitempty" mapstructure:"skip-verify,omitempty"`
	CAFile     string `json:"ca-file,omitempty" mapstructure:"ca-file,omitempty"`
	CertFile   string `json:"cert-file,omitempty" mapstructure:"cert-file,omitempty"`
	KeyFile    string `json:"key-file,omitempty" mapstructure:"key-file,omitempty"`
	// HTTP basicAuth
	Username string `json:"username,omitempty" mapstructure:"username,omitempty"`
	Password string `json:"password,omitempty" mapstructure:"password,omitempty"`
	// Oauth2
	Token string `json:"token,omitempty" mapstructure:"token,omitempty"`
	// if true, registers httpLoader prometheus metrics with the provided
	// prometheus registry
	EnableMetrics bool `json:"enable-metrics,omitempty" mapstructure:"enable-metrics,omitempty"`
	// enable Debug
	Debug bool `json:"debug,omitempty" mapstructure:"debug,omitempty"`
	// variables definitions to be passed to the actions
	Vars map[string]interface{}
	// variable file, values in this file will be overwritten by
	// the ones defined in Vars
	VarsFile string `mapstructure:"vars-file,omitempty"`
	// list of Actions to run on new target discovery
	OnAdd []string `json:"on-add,omitempty" mapstructure:"on-add,omitempty"`
	// list of Actions to run on target removal
	OnDelete []string `json:"on-delete,omitempty" mapstructure:"on-delete,omitempty"`
}

func (h *httpLoader) Init(ctx context.Context, cfg map[string]interface{}, logger *log.Logger, opts ...loaders.Option) error {
	err := loaders.DecodeConfig(cfg, h.cfg)
	if err != nil {
		return err
	}
	err = h.setDefaults()
	if err != nil {
		return err
	}
	for _, o := range opts {
		o(h)
	}
	if logger != nil {
		h.logger.SetOutput(logger.Writer())
		h.logger.SetFlags(logger.Flags())
	}
	err = h.readVars()
	if err != nil {
		return err
	}
	for _, actName := range h.cfg.OnAdd {
		if cfg, ok := h.actionsConfig[actName]; ok {
			a, err := h.initializeAction(cfg)
			if err != nil {
				return err
			}
			h.addActions = append(h.addActions, a)
			continue
		}
		return fmt.Errorf("unknown action name %q", actName)

	}
	for _, actName := range h.cfg.OnDelete {
		if cfg, ok := h.actionsConfig[actName]; ok {
			a, err := h.initializeAction(cfg)
			if err != nil {
				return err
			}
			h.delActions = append(h.delActions, a)
			continue
		}
		return fmt.Errorf("unknown action name %q", actName)
	}
	h.numActions = len(h.addActions) + len(h.delActions)
	return nil
}

func (h *httpLoader) Start(ctx context.Context) chan *loaders.TargetOperation {
	opChan := make(chan *loaders.TargetOperation)
	ticker := time.NewTicker(h.cfg.Interval)
	go func() {
		defer close(opChan)
		defer ticker.Stop()
		for {
			select {
			case <-ctx.Done():
				return
			case <-ticker.C:
				readTargets, err := h.getTargets()
				if err != nil {
					h.logger.Printf("failed to read targets from HTTP server: %v", err)
					continue
				}
				select {
				case <-ctx.Done():
					return
				default:
					h.updateTargets(ctx, readTargets, opChan)
				}
			}
		}
	}()
	return opChan
}

func (h *httpLoader) setDefaults() error {
	if h.cfg.URL == "" {
		return errors.New("missing URL")
	}
	if h.cfg.Interval <= 0 {
		h.cfg.Interval = defaultInterval
	}
	if h.cfg.Timeout <= 0 {
		h.cfg.Timeout = defaultTimeout
	}
	return nil
}

func (h *httpLoader) getTargets() (map[string]*types.TargetConfig, error) {
	c := resty.New()
	tlsCfg, err := utils.NewTLSConfig(h.cfg.CAFile, h.cfg.CertFile, h.cfg.KeyFile, h.cfg.SkipVerify, false)
	if err != nil {
		httpLoaderFailedGetRequests.WithLabelValues(loaderType, fmt.Sprintf("%v", err))
		return nil, err
	}
	if tlsCfg != nil {
		c = c.SetTLSClientConfig(tlsCfg)
	}
	c.SetTimeout(h.cfg.Timeout)
	if h.cfg.Username != "" && h.cfg.Password != "" {
		c.SetBasicAuth(h.cfg.Username, h.cfg.Password)
	}
	if h.cfg.Token != "" {
		c.SetAuthToken(h.cfg.Token)
	}
	result := make(map[string]*types.TargetConfig)
	start := time.Now()
	httpLoaderGetRequestsTotal.WithLabelValues(loaderType).Add(1)
	rsp, err := c.R().SetResult(result).Get(h.cfg.URL)
	if err != nil {
		return nil, err
	}
	httpLoaderGetRequestDuration.WithLabelValues(loaderType).Set(float64(time.Since(start).Nanoseconds()))
	if rsp.StatusCode() != 200 {
		httpLoaderFailedGetRequests.WithLabelValues(loaderType, rsp.Status())
		return nil, fmt.Errorf("failed request, code=%d", rsp.StatusCode())
	}
	return rsp.Result().(map[string]*types.TargetConfig), nil
}

func (h *httpLoader) updateTargets(ctx context.Context, tcs map[string]*types.TargetConfig, opChan chan *loaders.TargetOperation) {
	var err error
	for _, tc := range tcs {
		err = h.targetConfigFn(tc)
		if err != nil {
			h.logger.Printf("failed running target config fn on target %q", tc.Name)
		}
	}
	targetOp, err := h.runActions(ctx, tcs, loaders.Diff(h.lastTargets, tcs))
	if err != nil {
		h.logger.Printf("failed to run actions: %v", err)
		return
	}
	numAdds := len(targetOp.Add)
	numDels := len(targetOp.Del)
	defer func() {
		httpLoaderLoadedTargets.WithLabelValues(loaderType).Set(float64(numAdds))
		httpLoaderDeletedTargets.WithLabelValues(loaderType).Set(float64(numDels))
	}()
	if numAdds+numDels == 0 {
		return
	}
	h.m.Lock()
	for _, t := range targetOp.Add {
		if _, ok := h.lastTargets[t.Name]; !ok {
			h.lastTargets[t.Name] = t
		}
	}
	for _, n := range targetOp.Del {
		delete(h.lastTargets, n)
	}
	h.m.Unlock()
	opChan <- targetOp
}

func (h *httpLoader) readVars() error {
	if h.cfg.VarsFile == "" {
		h.vars = h.cfg.Vars
		return nil
	}
	b, err := utils.ReadFile(context.TODO(), h.cfg.VarsFile)
	if err != nil {
		return err
	}
	v := make(map[string]interface{})
	err = yaml.Unmarshal(b, &v)
	if err != nil {
		return err
	}
	h.vars = utils.MergeMaps(v, h.cfg.Vars)
	return nil
}

func (h *httpLoader) initializeAction(cfg map[string]interface{}) (actions.Action, error) {
	if len(cfg) == 0 {
		return nil, errors.New("missing action definition")
	}
	if actType, ok := cfg["type"]; ok {
		switch actType := actType.(type) {
		case string:
			if in, ok := actions.Actions[actType]; ok {
				act := in()
				err := act.Init(cfg, actions.WithLogger(h.logger), actions.WithTargets(nil))
				if err != nil {
					return nil, err
				}

				return act, nil
			}
			return nil, fmt.Errorf("unknown action type %q", actType)
		default:
			return nil, fmt.Errorf("unexpected action field type %T", actType)
		}
	}
	return nil, errors.New("missing type field under action")
}

func (f *httpLoader) runActions(ctx context.Context, tcs map[string]*types.TargetConfig, targetOp *loaders.TargetOperation) (*loaders.TargetOperation, error) {
	if f.numActions == 0 {
		return targetOp, nil
	}
	opChan := make(chan *loaders.TargetOperation)
	// some actions are defined,
	doneCh := make(chan struct{})
	result := &loaders.TargetOperation{
		Add: make([]*types.TargetConfig, 0, len(targetOp.Add)),
		Del: make([]string, 0, len(targetOp.Del)),
	}
	// start operation gathering goroutine
	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			case op, ok := <-opChan:
				if !ok {
					close(doneCh)
					return
				}
				result.Add = append(result.Add, op.Add...)
				result.Del = append(result.Del, op.Del...)
			}
		}
	}()
	// create waitGroup and add the number of target operations to it
	wg := new(sync.WaitGroup)
	wg.Add(len(targetOp.Add) + len(targetOp.Del))
	// run OnAdd actions
	for _, tAdd := range targetOp.Add {
		go func(tc *types.TargetConfig) {
			defer wg.Done()
			err := f.runOnAddActions(tc.Name, tcs)
			if err != nil {
				f.logger.Printf("failed running OnAdd actions: %v", err)
				return
			}
			opChan <- &loaders.TargetOperation{Add: []*types.TargetConfig{tc}}
		}(tAdd)
	}
	// run OnDelete actions
	for _, tDel := range targetOp.Del {
		go func(name string) {
			defer wg.Done()
			err := f.runOnDeleteActions(name, tcs)
			if err != nil {
				f.logger.Printf("failed running OnDelete actions: %v", err)
				return
			}
			opChan <- &loaders.TargetOperation{Del: []string{name}}
		}(tDel)
	}
	wg.Wait()
	close(opChan)
	<-doneCh //wait for gathering goroutine to finish
	return result, nil
}

func (d *httpLoader) runOnAddActions(tName string, tcs map[string]*types.TargetConfig) error {
	aCtx := &actions.Context{
		Input:   tName,
		Env:     make(map[string]interface{}),
		Vars:    d.vars,
		Targets: tcs,
	}
	for _, act := range d.addActions {
		d.logger.Printf("running action %q for target %q", act.NName(), tName)
		res, err := act.Run(aCtx)
		if err != nil {
			// delete target from known targets map
			d.m.Lock()
			delete(d.lastTargets, tName)
			d.m.Unlock()
			return fmt.Errorf("action %q for target %q failed: %v", act.NName(), tName, err)
		}

		aCtx.Env[act.NName()] = utils.Convert(res)
		if d.cfg.Debug {
			d.logger.Printf("action %q, target %q result: %+v", act.NName(), tName, res)
			b, _ := json.MarshalIndent(aCtx, "", "  ")
			d.logger.Printf("action %q context:\n%s", act.NName(), string(b))
		}
	}
	return nil
}

func (d *httpLoader) runOnDeleteActions(tName string, tcs map[string]*types.TargetConfig) error {
	env := make(map[string]interface{})
	for _, act := range d.delActions {
		res, err := act.Run(&actions.Context{Input: tName, Env: env, Vars: d.vars})
		if err != nil {
			return fmt.Errorf("action %q for target %q failed: %v", act.NName(), tName, err)
		}
		env[act.NName()] = res
	}
	return nil
}
<commit_msg>add template to http loader<commit_after>package http_loader

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"sync"
	"text/template"
	"time"

	"github.com/go-resty/resty/v2"
	"github.com/karimra/gnmic/actions"
	"github.com/karimra/gnmic/loaders"
	"github.com/karimra/gnmic/types"
	"github.com/karimra/gnmic/utils"
	"gopkg.in/yaml.v2"
)

const (
	loggingPrefix   = "[http_loader] "
	loaderType      = "http"
	defaultInterval = 1 * time.Minute
	defaultTimeout  = 50 * time.Second
)

func init() {
	loaders.Register(loaderType, func() loaders.TargetLoader {
		return &httpLoader{
			cfg:         &cfg{},
			lastTargets: make(map[string]*types.TargetConfig),
			logger:      log.New(io.Discard, loggingPrefix, utils.DefaultLoggingFlags),
		}
	})
}

type httpLoader struct {
	cfg            *cfg
	m              *sync.RWMutex
	lastTargets    map[string]*types.TargetConfig
	targetConfigFn func(*types.TargetConfig) error
	logger         *log.Logger
	//
	tpl           *template.Template
	vars          map[string]interface{}
	actionsConfig map[string]map[string]interface{}
	addActions    []actions.Action
	delActions    []actions.Action
	numActions    int
}

type cfg struct {
	// the server URL, must include http or https as a prefix
	URL string `json:"url,omitempty" mapstructure:"url,omitempty"`
	// server query interval
	Interval time.Duration `json:"interval,omitempty" mapstructure:"interval,omitempty"`
	// query timeout
	Timeout time.Duration `json:"timeout,omitempty" mapstructure:"timeout,omitempty"`
	// TLS config
	SkipVerify bool   `json:"skip-verify,omitempty" mapstructure:"skip-verify,omitempty"`
	CAFile     string `json:"ca-file,omitempty" mapstructure:"ca-file,omitempty"`
	CertFile   string `json:"cert-file,omitempty" mapstructure:"cert-file,omitempty"`
	KeyFile    string `json:"key-file,omitempty" mapstructure:"key-file,omitempty"`
	// HTTP basicAuth
	Username string `json:"username,omitempty" mapstructure:"username,omitempty"`
	Password string `json:"password,omitempty" mapstructure:"password,omitempty"`
	// Oauth2
	Token string `json:"token,omitempty" mapstructure:"token,omitempty"`
	// a Go text template that can be used to transform the targets format
	// read from the remote http server to match gNMIc's expected format.
	Template string `json:"template,omitempty" mapstructure:"template,omitempty"`
	// if true, registers httpLoader prometheus metrics with the provided
	// prometheus registry
	EnableMetrics bool `json:"enable-metrics,omitempty" mapstructure:"enable-metrics,omitempty"`
	// enable Debug
	Debug bool `json:"debug,omitempty" mapstructure:"debug,omitempty"`
	// variables definitions to be passed to the actions
	Vars map[string]interface{}
	// variable file, values in this file will be overwritten by
	// the ones defined in Vars
	VarsFile string `mapstructure:"vars-file,omitempty"`
	// list of Actions to run on new target discovery
	OnAdd []string `json:"on-add,omitempty" mapstructure:"on-add,omitempty"`
	// list of Actions to run on target removal
	OnDelete []string `json:"on-delete,omitempty" mapstructure:"on-delete,omitempty"`
}

func (h *httpLoader) Init(ctx context.Context, cfg map[string]interface{}, logger *log.Logger, opts ...loaders.Option) error {
	err := loaders.DecodeConfig(cfg, h.cfg)
	if err != nil {
		return err
	}
	err = h.setDefaults()
	if err != nil {
		return err
	}
	for _, o := range opts {
		o(h)
	}
	if logger != nil {
		h.logger.SetOutput(logger.Writer())
		h.logger.SetFlags(logger.Flags())
	}
	if h.cfg.Template != "" {
		h.tpl, err = utils.CreateTemplate("http-loader-template", h.cfg.Template)
		if err != nil {
			return err
		}
	}
	err = h.readVars()
	if err != nil {
		return err
	}
	for _, actName := range h.cfg.OnAdd {
		if cfg, ok := h.actionsConfig[actName]; ok {
			a, err := h.initializeAction(cfg)
			if err != nil {
				return err
			}
			h.addActions = append(h.addActions, a)
			continue
		}
		return fmt.Errorf("unknown action name %q", actName)

	}
	for _, actName := range h.cfg.OnDelete {
		if cfg, ok := h.actionsConfig[actName]; ok {
			a, err := h.initializeAction(cfg)
			if err != nil {
				return err
			}
			h.delActions = append(h.delActions, a)
			continue
		}
		return fmt.Errorf("unknown action name %q", actName)
	}
	h.numActions = len(h.addActions) + len(h.delActions)
	return nil
}

func (h *httpLoader) Start(ctx context.Context) chan *loaders.TargetOperation {
	opChan := make(chan *loaders.TargetOperation)
	ticker := time.NewTicker(h.cfg.Interval)
	go func() {
		defer close(opChan)
		defer ticker.Stop()
		for {
			select {
			case <-ctx.Done():
				return
			case <-ticker.C:
				readTargets, err := h.getTargets()
				if err != nil {
					h.logger.Printf("failed to read targets from HTTP server: %v", err)
					continue
				}
				select {
				case <-ctx.Done():
					return
				default:
					h.updateTargets(ctx, readTargets, opChan)
				}
			}
		}
	}()
	return opChan
}

func (h *httpLoader) setDefaults() error {
	if h.cfg.URL == "" {
		return errors.New("missing URL")
	}
	if h.cfg.Interval <= 0 {
		h.cfg.Interval = defaultInterval
	}
	if h.cfg.Timeout <= 0 {
		h.cfg.Timeout = defaultTimeout
	}
	return nil
}

func (h *httpLoader) getTargets() (map[string]*types.TargetConfig, error) {
	c := resty.New()
	tlsCfg, err := utils.NewTLSConfig(h.cfg.CAFile, h.cfg.CertFile, h.cfg.KeyFile, h.cfg.SkipVerify, false)
	if err != nil {
		httpLoaderFailedGetRequests.WithLabelValues(loaderType, fmt.Sprintf("%v", err))
		return nil, err
	}
	if tlsCfg != nil {
		c = c.SetTLSClientConfig(tlsCfg)
	}
	c.SetTimeout(h.cfg.Timeout)
	if h.cfg.Username != "" && h.cfg.Password != "" {
		c.SetBasicAuth(h.cfg.Username, h.cfg.Password)
	}
	if h.cfg.Token != "" {
		c.SetAuthToken(h.cfg.Token)
	}
	start := time.Now()
	httpLoaderGetRequestsTotal.WithLabelValues(loaderType).Add(1)
	rsp, err := c.R().Get(h.cfg.URL)
	if err != nil {
		return nil, err
	}
	httpLoaderGetRequestDuration.WithLabelValues(loaderType).Set(float64(time.Since(start).Nanoseconds()))
	if rsp.StatusCode() != 200 {
		httpLoaderFailedGetRequests.WithLabelValues(loaderType, rsp.Status())
		return nil, fmt.Errorf("failed request, code=%d", rsp.StatusCode())
	}
	b := rsp.Body()
	if h.tpl != nil {
		var input interface{}
		err = json.Unmarshal(b, input)
		if err != nil {
			httpLoaderFailedGetRequests.WithLabelValues(loaderType, fmt.Sprintf("%v", err)).Add(1)
			return nil, err
		}
		buf := new(bytes.Buffer)
		err = h.tpl.Execute(buf, input)
		if err != nil {
			httpLoaderFailedGetRequests.WithLabelValues(loaderType, fmt.Sprintf("%v", err)).Add(1)
			return nil, err
		}
		b = buf.Bytes()
	}

	result := make(map[string]*types.TargetConfig)
	// unmarshal the bytes into a map of targetConfigs
	err = yaml.Unmarshal(b, result)
	if err != nil {
		httpLoaderFailedGetRequests.WithLabelValues(loaderType, fmt.Sprintf("%v", err)).Add(1)
		return nil, err
	}
	// properly initialize address and name if not set
	for n, t := range result {
		if t == nil && n != "" {
			result[n] = &types.TargetConfig{
				Name:    n,
				Address: n,
			}
			continue
		}
		if t.Name == "" {
			t.Name = n
		}
		if t.Address == "" {
			t.Address = n
		}
	}
	h.logger.Printf("result: %v", result)
	return result, nil
}

func (h *httpLoader) updateTargets(ctx context.Context, tcs map[string]*types.TargetConfig, opChan chan *loaders.TargetOperation) {
	var err error
	for _, tc := range tcs {
		err = h.targetConfigFn(tc)
		if err != nil {
			h.logger.Printf("failed running target config fn on target %q", tc.Name)
		}
	}
	targetOp, err := h.runActions(ctx, tcs, loaders.Diff(h.lastTargets, tcs))
	if err != nil {
		h.logger.Printf("failed to run actions: %v", err)
		return
	}
	numAdds := len(targetOp.Add)
	numDels := len(targetOp.Del)
	defer func() {
		httpLoaderLoadedTargets.WithLabelValues(loaderType).Set(float64(numAdds))
		httpLoaderDeletedTargets.WithLabelValues(loaderType).Set(float64(numDels))
	}()
	if numAdds+numDels == 0 {
		return
	}
	h.m.Lock()
	for _, t := range targetOp.Add {
		if _, ok := h.lastTargets[t.Name]; !ok {
			h.lastTargets[t.Name] = t
		}
	}
	for _, n := range targetOp.Del {
		delete(h.lastTargets, n)
	}
	h.m.Unlock()
	opChan <- targetOp
}

func (h *httpLoader) readVars() error {
	if h.cfg.VarsFile == "" {
		h.vars = h.cfg.Vars
		return nil
	}
	b, err := utils.ReadFile(context.TODO(), h.cfg.VarsFile)
	if err != nil {
		return err
	}
	v := make(map[string]interface{})
	err = yaml.Unmarshal(b, &v)
	if err != nil {
		return err
	}
	h.vars = utils.MergeMaps(v, h.cfg.Vars)
	return nil
}

func (h *httpLoader) initializeAction(cfg map[string]interface{}) (actions.Action, error) {
	if len(cfg) == 0 {
		return nil, errors.New("missing action definition")
	}
	if actType, ok := cfg["type"]; ok {
		switch actType := actType.(type) {
		case string:
			if in, ok := actions.Actions[actType]; ok {
				act := in()
				err := act.Init(cfg, actions.WithLogger(h.logger), actions.WithTargets(nil))
				if err != nil {
					return nil, err
				}

				return act, nil
			}
			return nil, fmt.Errorf("unknown action type %q", actType)
		default:
			return nil, fmt.Errorf("unexpected action field type %T", actType)
		}
	}
	return nil, errors.New("missing type field under action")
}

func (f *httpLoader) runActions(ctx context.Context, tcs map[string]*types.TargetConfig, targetOp *loaders.TargetOperation) (*loaders.TargetOperation, error) {
	if f.numActions == 0 {
		return targetOp, nil
	}
	opChan := make(chan *loaders.TargetOperation)
	// some actions are defined,
	doneCh := make(chan struct{})
	result := &loaders.TargetOperation{
		Add: make([]*types.TargetConfig, 0, len(targetOp.Add)),
		Del: make([]string, 0, len(targetOp.Del)),
	}
	// start operation gathering goroutine
	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			case op, ok := <-opChan:
				if !ok {
					close(doneCh)
					return
				}
				result.Add = append(result.Add, op.Add...)
				result.Del = append(result.Del, op.Del...)
			}
		}
	}()
	// create waitGroup and add the number of target operations to it
	wg := new(sync.WaitGroup)
	wg.Add(len(targetOp.Add) + len(targetOp.Del))
	// run OnAdd actions
	for _, tAdd := range targetOp.Add {
		go func(tc *types.TargetConfig) {
			defer wg.Done()
			err := f.runOnAddActions(tc.Name, tcs)
			if err != nil {
				f.logger.Printf("failed running OnAdd actions: %v", err)
				return
			}
			opChan <- &loaders.TargetOperation{Add: []*types.TargetConfig{tc}}
		}(tAdd)
	}
	// run OnDelete actions
	for _, tDel := range targetOp.Del {
		go func(name string) {
			defer wg.Done()
			err := f.runOnDeleteActions(name, tcs)
			if err != nil {
				f.logger.Printf("failed running OnDelete actions: %v", err)
				return
			}
			opChan <- &loaders.TargetOperation{Del: []string{name}}
		}(tDel)
	}
	wg.Wait()
	close(opChan)
	<-doneCh //wait for gathering goroutine to finish
	return result, nil
}

func (d *httpLoader) runOnAddActions(tName string, tcs map[string]*types.TargetConfig) error {
	aCtx := &actions.Context{
		Input:   tName,
		Env:     make(map[string]interface{}),
		Vars:    d.vars,
		Targets: tcs,
	}
	for _, act := range d.addActions {
		d.logger.Printf("running action %q for target %q", act.NName(), tName)
		res, err := act.Run(aCtx)
		if err != nil {
			// delete target from known targets map
			d.m.Lock()
			delete(d.lastTargets, tName)
			d.m.Unlock()
			return fmt.Errorf("action %q for target %q failed: %v", act.NName(), tName, err)
		}

		aCtx.Env[act.NName()] = utils.Convert(res)
		if d.cfg.Debug {
			d.logger.Printf("action %q, target %q result: %+v", act.NName(), tName, res)
			b, _ := json.MarshalIndent(aCtx, "", "  ")
			d.logger.Printf("action %q context:\n%s", act.NName(), string(b))
		}
	}
	return nil
}

func (d *httpLoader) runOnDeleteActions(tName string, tcs map[string]*types.TargetConfig) error {
	env := make(map[string]interface{})
	for _, act := range d.delActions {
		res, err := act.Run(&actions.Context{Input: tName, Env: env, Vars: d.vars})
		if err != nil {
			return fmt.Errorf("action %q for target %q failed: %v", act.NName(), tName, err)
		}
		env[act.NName()] = res
	}
	return nil
}
<|endoftext|>


<commit_before>package app

import (
	"github.com/jinzhu/gorm"

	"github.com/revel/revel"
)

var (
	DB *gorm.DB
)

func InitDB() *gorm.DB {
	var (
		driver string
		spec   string
		found  bool
	)

	// Read configuration
	if driver, found = revel.Config.String("db.driver"); !found {
		revel.ERROR.Fatal("No db.driver found.")
	}
	if spec, found = revel.Config.String("db.spec"); !found {
		revel.ERROR.Fatal("No db.spec found.")
	}

	maxIdleConns := revel.Config.IntDefault("db.max_idle_conns", 10)
	maxOpenConns := revel.Config.IntDefault("db.max_open_conns", 100)
	singularTable := revel.Config.BoolDefault("db.singular_table", false)
	logMode := revel.Config.BoolDefault("db.log_mode", false)

	// Initialize `gorm`
	dbm, err := gorm.Open(driver, spec)
	if err != nil {
		revel.ERROR.Fatal(err)
	}

	DB = &dbm

	dbm.DB().Ping()
	dbm.DB().SetMaxIdleConns(maxIdleConns)
	dbm.DB().SetMaxOpenConns(maxOpenConns)
	dbm.SingularTable(singularTable)
	dbm.LogMode(logMode)

	return &dbm
}
<commit_msg>Improve logging<commit_after>package app

import (
	"github.com/jinzhu/gorm"

	"github.com/revel/revel"
)

var (
	DB *gorm.DB
)

func InitDB() *gorm.DB {
	var (
		driver string
		spec   string
		found  bool
	)

	// Read configuration
	if driver, found = revel.Config.String("db.driver"); !found {
		revel.ERROR.Fatal("No db.driver found.")
	}
	if spec, found = revel.Config.String("db.spec"); !found {
		revel.ERROR.Fatal("No db.spec found.")
	}

	maxIdleConns := revel.Config.IntDefault("db.max_idle_conns", 10)
	maxOpenConns := revel.Config.IntDefault("db.max_open_conns", 100)
	singularTable := revel.Config.BoolDefault("db.singular_table", false)
	logMode := revel.Config.BoolDefault("db.log_mode", false)

	// Initialize `gorm`
	dbm, err := gorm.Open(driver, spec)
	if err != nil {
		revel.ERROR.Fatal(err)
	}

	DB = &dbm

	dbm.DB().Ping()
	dbm.DB().SetMaxIdleConns(maxIdleConns)
	dbm.DB().SetMaxOpenConns(maxOpenConns)
	dbm.SingularTable(singularTable)
	dbm.LogMode(logMode)
	dbm.SetLogger(gorm.Logger{revel.INFO})

	return &dbm
}
<|endoftext|>


<commit_before>package checker_test

import (
	"io"
	"math/rand"
	"path/filepath"
	"sort"
	"testing"

	"restic"
	"restic/archiver"
	"restic/checker"
	"restic/repository"
	"restic/test"
)

var checkerTestData = filepath.Join("testdata", "checker-test-repo.tar.gz")

func collectErrors(f func(chan<- error, <-chan struct{})) (errs []error) {
	done := make(chan struct{})
	defer close(done)

	errChan := make(chan error)

	go f(errChan, done)

	for err := range errChan {
		errs = append(errs, err)
	}

	return errs
}

func checkPacks(chkr *checker.Checker) []error {
	return collectErrors(chkr.Packs)
}

func checkStruct(chkr *checker.Checker) []error {
	return collectErrors(chkr.Structure)
}

func checkData(chkr *checker.Checker) []error {
	return collectErrors(
		func(errCh chan<- error, done <-chan struct{}) {
			chkr.ReadData(nil, errCh, done)
		},
	)
}

func TestCheckRepo(t *testing.T) {
	repodir, cleanup := test.Env(t, checkerTestData)
	defer cleanup()

	repo := repository.TestOpenLocal(t, repodir)

	chkr := checker.New(repo)
	hints, errs := chkr.LoadIndex()
	if len(errs) > 0 {
		t.Fatalf("expected no errors, got %v: %v", len(errs), errs)
	}

	if len(hints) > 0 {
		t.Errorf("expected no hints, got %v: %v", len(hints), hints)
	}

	test.OKs(t, checkPacks(chkr))
	test.OKs(t, checkStruct(chkr))
}

func TestMissingPack(t *testing.T) {
	repodir, cleanup := test.Env(t, checkerTestData)
	defer cleanup()

	repo := repository.TestOpenLocal(t, repodir)

	packHandle := restic.Handle{
		Type: restic.DataFile,
		Name: "657f7fb64f6a854fff6fe9279998ee09034901eded4e6db9bcee0e59745bbce6",
	}
	test.OK(t, repo.Backend().Remove(packHandle))

	chkr := checker.New(repo)
	hints, errs := chkr.LoadIndex()
	if len(errs) > 0 {
		t.Fatalf("expected no errors, got %v: %v", len(errs), errs)
	}

	if len(hints) > 0 {
		t.Errorf("expected no hints, got %v: %v", len(hints), hints)
	}

	errs = checkPacks(chkr)

	test.Assert(t, len(errs) == 1,
		"expected exactly one error, got %v", len(errs))

	if err, ok := errs[0].(checker.PackError); ok {
		test.Equals(t, packHandle.Name, err.ID.String())
	} else {
		t.Errorf("expected error returned by checker.Packs() to be PackError, got %v", err)
	}
}

func TestUnreferencedPack(t *testing.T) {
	repodir, cleanup := test.Env(t, checkerTestData)
	defer cleanup()

	repo := repository.TestOpenLocal(t, repodir)

	// index 3f1a only references pack 60e0
	packID := "60e0438dcb978ec6860cc1f8c43da648170ee9129af8f650f876bad19f8f788e"
	indexHandle := restic.Handle{
		Type: restic.IndexFile,
		Name: "3f1abfcb79c6f7d0a3be517d2c83c8562fba64ef2c8e9a3544b4edaf8b5e3b44",
	}
	test.OK(t, repo.Backend().Remove(indexHandle))

	chkr := checker.New(repo)
	hints, errs := chkr.LoadIndex()
	if len(errs) > 0 {
		t.Fatalf("expected no errors, got %v: %v", len(errs), errs)
	}

	if len(hints) > 0 {
		t.Errorf("expected no hints, got %v: %v", len(hints), hints)
	}

	errs = checkPacks(chkr)

	test.Assert(t, len(errs) == 1,
		"expected exactly one error, got %v", len(errs))

	if err, ok := errs[0].(checker.PackError); ok {
		test.Equals(t, packID, err.ID.String())
	} else {
		t.Errorf("expected error returned by checker.Packs() to be PackError, got %v", err)
	}
}

func TestUnreferencedBlobs(t *testing.T) {
	repodir, cleanup := test.Env(t, checkerTestData)
	defer cleanup()

	repo := repository.TestOpenLocal(t, repodir)

	snapshotHandle := restic.Handle{
		Type: restic.SnapshotFile,
		Name: "51d249d28815200d59e4be7b3f21a157b864dc343353df9d8e498220c2499b02",
	}
	test.OK(t, repo.Backend().Remove(snapshotHandle))

	unusedBlobsBySnapshot := restic.IDs{
		restic.TestParseID("58c748bbe2929fdf30c73262bd8313fe828f8925b05d1d4a87fe109082acb849"),
		restic.TestParseID("988a272ab9768182abfd1fe7d7a7b68967825f0b861d3b36156795832c772235"),
		restic.TestParseID("c01952de4d91da1b1b80bc6e06eaa4ec21523f4853b69dc8231708b9b7ec62d8"),
		restic.TestParseID("bec3a53d7dc737f9a9bee68b107ec9e8ad722019f649b34d474b9982c3a3fec7"),
		restic.TestParseID("2a6f01e5e92d8343c4c6b78b51c5a4dc9c39d42c04e26088c7614b13d8d0559d"),
		restic.TestParseID("18b51b327df9391732ba7aaf841a4885f350d8a557b2da8352c9acf8898e3f10"),
	}

	sort.Sort(unusedBlobsBySnapshot)

	chkr := checker.New(repo)
	hints, errs := chkr.LoadIndex()
	if len(errs) > 0 {
		t.Fatalf("expected no errors, got %v: %v", len(errs), errs)
	}

	if len(hints) > 0 {
		t.Errorf("expected no hints, got %v: %v", len(hints), hints)
	}

	test.OKs(t, checkPacks(chkr))
	test.OKs(t, checkStruct(chkr))

	blobs := chkr.UnusedBlobs()
	sort.Sort(blobs)

	test.Equals(t, unusedBlobsBySnapshot, blobs)
}

var checkerDuplicateIndexTestData = filepath.Join("testdata", "duplicate-packs-in-index-test-repo.tar.gz")

func TestDuplicatePacksInIndex(t *testing.T) {
	repodir, cleanup := test.Env(t, checkerDuplicateIndexTestData)
	defer cleanup()

	repo := repository.TestOpenLocal(t, repodir)

	chkr := checker.New(repo)
	hints, errs := chkr.LoadIndex()
	if len(hints) == 0 {
		t.Fatalf("did not get expected checker hints for duplicate packs in indexes")
	}

	found := false
	for _, hint := range hints {
		if _, ok := hint.(checker.ErrDuplicatePacks); ok {
			found = true
		} else {
			t.Errorf("got unexpected hint: %v", hint)
		}
	}

	if !found {
		t.Fatalf("did not find hint ErrDuplicatePacks")
	}

	if len(errs) > 0 {
		t.Errorf("expected no errors, got %v: %v", len(errs), errs)
	}
}

// errorBackend randomly modifies data after reading.
type errorBackend struct {
	restic.Backend
	ProduceErrors bool
}

func (b errorBackend) Load(h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
	rd, err := b.Backend.Load(h, length, offset)
	if err != nil {
		return rd, err
	}

	if b.ProduceErrors {
		return errorReadCloser{rd}, err
	}

	return rd, nil
}

type errorReadCloser struct {
	io.ReadCloser
}

func (erd errorReadCloser) Read(p []byte) (int, error) {
	n, err := erd.ReadCloser.Read(p)
	if n > 0 {
		induceError(p[:n])
	}
	return n, err
}

func (erd errorReadCloser) Close() error {
	return erd.ReadCloser.Close()
}

// induceError flips a bit in the slice.
func induceError(data []byte) {
	if rand.Float32() < 0.2 {
		return
	}

	pos := rand.Intn(len(data))
	data[pos] ^= 1
}

func TestCheckerModifiedData(t *testing.T) {
	repo, cleanup := repository.TestRepository(t)
	defer cleanup()

	arch := archiver.New(repo)
	_, id, err := arch.Snapshot(nil, []string{"."}, nil, "localhost", nil)
	test.OK(t, err)
	t.Logf("archived as %v", id.Str())

	beError := &errorBackend{Backend: repo.Backend()}
	checkRepo := repository.New(beError)
	test.OK(t, checkRepo.SearchKey(test.TestPassword, 5))

	chkr := checker.New(checkRepo)

	hints, errs := chkr.LoadIndex()
	if len(errs) > 0 {
		t.Fatalf("expected no errors, got %v: %v", len(errs), errs)
	}

	if len(hints) > 0 {
		t.Errorf("expected no hints, got %v: %v", len(hints), hints)
	}

	beError.ProduceErrors = true
	errFound := false
	for _, err := range checkPacks(chkr) {
		t.Logf("pack error: %v", err)
	}

	for _, err := range checkStruct(chkr) {
		t.Logf("struct error: %v", err)
	}

	for _, err := range checkData(chkr) {
		t.Logf("data error: %v", err)
		errFound = true
	}

	if !errFound {
		t.Fatal("no error found, checker is broken")
	}
}

func BenchmarkChecker(t *testing.B) {
	repodir, cleanup := test.Env(t, checkerTestData)
	defer cleanup()

	repo := repository.TestOpenLocal(t, repodir)

	chkr := checker.New(repo)
	hints, errs := chkr.LoadIndex()
	if len(errs) > 0 {
		t.Fatalf("expected no errors, got %v: %v", len(errs), errs)
	}

	if len(hints) > 0 {
		t.Errorf("expected no hints, got %v: %v", len(hints), hints)
	}

	t.ResetTimer()

	for i := 0; i < t.N; i++ {
		test.OKs(t, checkPacks(chkr))
		test.OKs(t, checkStruct(chkr))
		test.OKs(t, checkData(chkr))
	}
}
<commit_msg>Add check for modified index<commit_after>package checker_test

import (
	"io"
	"math/rand"
	"path/filepath"
	"sort"
	"testing"

	"restic"
	"restic/archiver"
	"restic/checker"
	"restic/repository"
	"restic/test"
)

var checkerTestData = filepath.Join("testdata", "checker-test-repo.tar.gz")

func collectErrors(f func(chan<- error, <-chan struct{})) (errs []error) {
	done := make(chan struct{})
	defer close(done)

	errChan := make(chan error)

	go f(errChan, done)

	for err := range errChan {
		errs = append(errs, err)
	}

	return errs
}

func checkPacks(chkr *checker.Checker) []error {
	return collectErrors(chkr.Packs)
}

func checkStruct(chkr *checker.Checker) []error {
	return collectErrors(chkr.Structure)
}

func checkData(chkr *checker.Checker) []error {
	return collectErrors(
		func(errCh chan<- error, done <-chan struct{}) {
			chkr.ReadData(nil, errCh, done)
		},
	)
}

func TestCheckRepo(t *testing.T) {
	repodir, cleanup := test.Env(t, checkerTestData)
	defer cleanup()

	repo := repository.TestOpenLocal(t, repodir)

	chkr := checker.New(repo)
	hints, errs := chkr.LoadIndex()
	if len(errs) > 0 {
		t.Fatalf("expected no errors, got %v: %v", len(errs), errs)
	}

	if len(hints) > 0 {
		t.Errorf("expected no hints, got %v: %v", len(hints), hints)
	}

	test.OKs(t, checkPacks(chkr))
	test.OKs(t, checkStruct(chkr))
}

func TestMissingPack(t *testing.T) {
	repodir, cleanup := test.Env(t, checkerTestData)
	defer cleanup()

	repo := repository.TestOpenLocal(t, repodir)

	packHandle := restic.Handle{
		Type: restic.DataFile,
		Name: "657f7fb64f6a854fff6fe9279998ee09034901eded4e6db9bcee0e59745bbce6",
	}
	test.OK(t, repo.Backend().Remove(packHandle))

	chkr := checker.New(repo)
	hints, errs := chkr.LoadIndex()
	if len(errs) > 0 {
		t.Fatalf("expected no errors, got %v: %v", len(errs), errs)
	}

	if len(hints) > 0 {
		t.Errorf("expected no hints, got %v: %v", len(hints), hints)
	}

	errs = checkPacks(chkr)

	test.Assert(t, len(errs) == 1,
		"expected exactly one error, got %v", len(errs))

	if err, ok := errs[0].(checker.PackError); ok {
		test.Equals(t, packHandle.Name, err.ID.String())
	} else {
		t.Errorf("expected error returned by checker.Packs() to be PackError, got %v", err)
	}
}

func TestUnreferencedPack(t *testing.T) {
	repodir, cleanup := test.Env(t, checkerTestData)
	defer cleanup()

	repo := repository.TestOpenLocal(t, repodir)

	// index 3f1a only references pack 60e0
	packID := "60e0438dcb978ec6860cc1f8c43da648170ee9129af8f650f876bad19f8f788e"
	indexHandle := restic.Handle{
		Type: restic.IndexFile,
		Name: "3f1abfcb79c6f7d0a3be517d2c83c8562fba64ef2c8e9a3544b4edaf8b5e3b44",
	}
	test.OK(t, repo.Backend().Remove(indexHandle))

	chkr := checker.New(repo)
	hints, errs := chkr.LoadIndex()
	if len(errs) > 0 {
		t.Fatalf("expected no errors, got %v: %v", len(errs), errs)
	}

	if len(hints) > 0 {
		t.Errorf("expected no hints, got %v: %v", len(hints), hints)
	}

	errs = checkPacks(chkr)

	test.Assert(t, len(errs) == 1,
		"expected exactly one error, got %v", len(errs))

	if err, ok := errs[0].(checker.PackError); ok {
		test.Equals(t, packID, err.ID.String())
	} else {
		t.Errorf("expected error returned by checker.Packs() to be PackError, got %v", err)
	}
}

func TestUnreferencedBlobs(t *testing.T) {
	repodir, cleanup := test.Env(t, checkerTestData)
	defer cleanup()

	repo := repository.TestOpenLocal(t, repodir)

	snapshotHandle := restic.Handle{
		Type: restic.SnapshotFile,
		Name: "51d249d28815200d59e4be7b3f21a157b864dc343353df9d8e498220c2499b02",
	}
	test.OK(t, repo.Backend().Remove(snapshotHandle))

	unusedBlobsBySnapshot := restic.IDs{
		restic.TestParseID("58c748bbe2929fdf30c73262bd8313fe828f8925b05d1d4a87fe109082acb849"),
		restic.TestParseID("988a272ab9768182abfd1fe7d7a7b68967825f0b861d3b36156795832c772235"),
		restic.TestParseID("c01952de4d91da1b1b80bc6e06eaa4ec21523f4853b69dc8231708b9b7ec62d8"),
		restic.TestParseID("bec3a53d7dc737f9a9bee68b107ec9e8ad722019f649b34d474b9982c3a3fec7"),
		restic.TestParseID("2a6f01e5e92d8343c4c6b78b51c5a4dc9c39d42c04e26088c7614b13d8d0559d"),
		restic.TestParseID("18b51b327df9391732ba7aaf841a4885f350d8a557b2da8352c9acf8898e3f10"),
	}

	sort.Sort(unusedBlobsBySnapshot)

	chkr := checker.New(repo)
	hints, errs := chkr.LoadIndex()
	if len(errs) > 0 {
		t.Fatalf("expected no errors, got %v: %v", len(errs), errs)
	}

	if len(hints) > 0 {
		t.Errorf("expected no hints, got %v: %v", len(hints), hints)
	}

	test.OKs(t, checkPacks(chkr))
	test.OKs(t, checkStruct(chkr))

	blobs := chkr.UnusedBlobs()
	sort.Sort(blobs)

	test.Equals(t, unusedBlobsBySnapshot, blobs)
}

func TestModifiedIndex(t *testing.T) {
	repodir, cleanup := test.Env(t, checkerTestData)
	defer cleanup()

	repo := repository.TestOpenLocal(t, repodir)

	done := make(chan struct{})
	defer close(done)

	h := restic.Handle{
		Type: restic.IndexFile,
		Name: "90f838b4ac28735fda8644fe6a08dbc742e57aaf81b30977b4fefa357010eafd",
	}
	f, err := repo.Backend().Load(h, 0, 0)
	test.OK(t, err)

	// save the index again with a modified name so that the hash doesn't match
	// the content any more
	h2 := restic.Handle{
		Type: restic.IndexFile,
		Name: "80f838b4ac28735fda8644fe6a08dbc742e57aaf81b30977b4fefa357010eafd",
	}
	err = repo.Backend().Save(h2, f)
	test.OK(t, err)

	test.OK(t, f.Close())

	chkr := checker.New(repo)
	hints, errs := chkr.LoadIndex()
	if len(errs) == 0 {
		t.Fatalf("expected errors not found")
	}

	for _, err := range errs {
		t.Logf("found expected error %v", err)
	}

	if len(hints) > 0 {
		t.Errorf("expected no hints, got %v: %v", len(hints), hints)
	}
}

var checkerDuplicateIndexTestData = filepath.Join("testdata", "duplicate-packs-in-index-test-repo.tar.gz")

func TestDuplicatePacksInIndex(t *testing.T) {
	repodir, cleanup := test.Env(t, checkerDuplicateIndexTestData)
	defer cleanup()

	repo := repository.TestOpenLocal(t, repodir)

	chkr := checker.New(repo)
	hints, errs := chkr.LoadIndex()
	if len(hints) == 0 {
		t.Fatalf("did not get expected checker hints for duplicate packs in indexes")
	}

	found := false
	for _, hint := range hints {
		if _, ok := hint.(checker.ErrDuplicatePacks); ok {
			found = true
		} else {
			t.Errorf("got unexpected hint: %v", hint)
		}
	}

	if !found {
		t.Fatalf("did not find hint ErrDuplicatePacks")
	}

	if len(errs) > 0 {
		t.Errorf("expected no errors, got %v: %v", len(errs), errs)
	}
}

// errorBackend randomly modifies data after reading.
type errorBackend struct {
	restic.Backend
	ProduceErrors bool
}

func (b errorBackend) Load(h restic.Handle, length int, offset int64) (io.ReadCloser, error) {
	rd, err := b.Backend.Load(h, length, offset)
	if err != nil {
		return rd, err
	}

	if b.ProduceErrors {
		return errorReadCloser{rd}, err
	}

	return rd, nil
}

type errorReadCloser struct {
	io.ReadCloser
}

func (erd errorReadCloser) Read(p []byte) (int, error) {
	n, err := erd.ReadCloser.Read(p)
	if n > 0 {
		induceError(p[:n])
	}
	return n, err
}

func (erd errorReadCloser) Close() error {
	return erd.ReadCloser.Close()
}

// induceError flips a bit in the slice.
func induceError(data []byte) {
	if rand.Float32() < 0.2 {
		return
	}

	pos := rand.Intn(len(data))
	data[pos] ^= 1
}

func TestCheckerModifiedData(t *testing.T) {
	repo, cleanup := repository.TestRepository(t)
	defer cleanup()

	arch := archiver.New(repo)
	_, id, err := arch.Snapshot(nil, []string{"."}, nil, "localhost", nil)
	test.OK(t, err)
	t.Logf("archived as %v", id.Str())

	beError := &errorBackend{Backend: repo.Backend()}
	checkRepo := repository.New(beError)
	test.OK(t, checkRepo.SearchKey(test.TestPassword, 5))

	chkr := checker.New(checkRepo)

	hints, errs := chkr.LoadIndex()
	if len(errs) > 0 {
		t.Fatalf("expected no errors, got %v: %v", len(errs), errs)
	}

	if len(hints) > 0 {
		t.Errorf("expected no hints, got %v: %v", len(hints), hints)
	}

	beError.ProduceErrors = true
	errFound := false
	for _, err := range checkPacks(chkr) {
		t.Logf("pack error: %v", err)
	}

	for _, err := range checkStruct(chkr) {
		t.Logf("struct error: %v", err)
	}

	for _, err := range checkData(chkr) {
		t.Logf("data error: %v", err)
		errFound = true
	}

	if !errFound {
		t.Fatal("no error found, checker is broken")
	}
}

func BenchmarkChecker(t *testing.B) {
	repodir, cleanup := test.Env(t, checkerTestData)
	defer cleanup()

	repo := repository.TestOpenLocal(t, repodir)

	chkr := checker.New(repo)
	hints, errs := chkr.LoadIndex()
	if len(errs) > 0 {
		t.Fatalf("expected no errors, got %v: %v", len(errs), errs)
	}

	if len(hints) > 0 {
		t.Errorf("expected no hints, got %v: %v", len(hints), hints)
	}

	t.ResetTimer()

	for i := 0; i < t.N; i++ {
		test.OKs(t, checkPacks(chkr))
		test.OKs(t, checkStruct(chkr))
		test.OKs(t, checkData(chkr))
	}
}
<|endoftext|>


<commit_before>package commands

import (
	"fmt"
	"os/exec"
	"path"
	"time"

	"rollcage/core"

	"github.com/cactus/cobra"
	"github.com/cactus/gologit"
)

func startCmdRun(cmd *cobra.Command, args []string) {
	// requires root
	if !core.IsRoot() {
		gologit.Fatalf("Must be root to stop\n")
	}

	jail, err := core.FindJail(args[0])
	if err != nil {
		gologit.Fatalf("No jail found by '%s'\n", args[0])
	}

	if jail.IsRunning() {
		gologit.Fatalf("Jail is already running!\n")
	}

	props := jail.GetProperties()

	// set a default path
	environ := []string{
		"PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin",
	}

	fmt.Printf("* Starting %s (%s)\n", jail.HostUUID, jail.Tag)
	// mount procfs
	if props.GetIOC("mount_procfs") == "1" {
		fmt.Printf("  + mounting procfs\n")
		procpath := path.Join(jail.Mountpoint, "root/proc")
		excmd := exec.Command("/sbin/mount", "-t", "procfs", "proc", procpath)
		excmd.Env = environ
		err := excmd.Run()
		if err != nil {
			gologit.Printf("%s\n", err)
		}
	}

	// prepare jail zfs dataset if enabled
	if props.GetIOC("jail_zfs") == "on" {
		fmt.Printf("  + jailing zfs dataset\n")
		setprops := core.ZFSProperties{
			"org.freebsd.iocage:allow_mount":     "1",
			"org.freebsd.iocage:allow_mount_zfs": "1",
			"org.freebsd.iocage:enforce_statfs":  "1",
		}
		jail.SetProperties(setprops)
		core.ZFSMust(
			fmt.Errorf("Error setting property"),
			"set", "jailed=on",
			path.Join(core.GetZFSRootPath(), props.GetIOC("jail_zfs_dataset")))
	}

	// bring up networking (vnet or legacy)
	ip4_addr_propline := "ip4.addr="
	ip6_addr_propline := "ip6.addr="
	if props.GetIOC("vnet") == "on" {
		fmt.Printf("  + Configuring VNET\n")
		// start VNET networking
	} else {
		// start standard networking (legacy?)
		if props.GetIOC("ip4_addr") != "none" {
			ip4_addr_propline += props.GetIOC("ip4_addr")
		}
		if props.GetIOC("ip6_addr") != "none" {
			ip6_addr_propline += props.GetIOC("ip6_addr")
		}
	}

	// get log dir

	logdir := core.ZFSMust(
		fmt.Errorf("Error setting property"),
		"get", "-H", "-o", "value", "mountpoint", core.GetZFSRootPath())
	logdir = path.Join(logdir, "log")

	logpath := path.Join(logdir, fmt.Sprintf("%s-console.log", jail.HostUUID))

	// start jail
	jailexec := []string{
		"/usr/sbin/jail", "-c",
		ip4_addr_propline,
		fmt.Sprintf("ip4.saddrsel=%s", props.GetIOC("ip4_saddrsel")),
		fmt.Sprintf("ip4=%s", props.GetIOC("ip4")),
		ip6_addr_propline,
		fmt.Sprintf("ip6.saddrsel=%s", props.GetIOC("ip6_saddrsel")),
		fmt.Sprintf("ip6=%s", props.GetIOC("ip6")),
		fmt.Sprintf("name=ioc-%s", jail.HostUUID),
		fmt.Sprintf("host.hostname=%s", props.GetIOC("hostname")),
		fmt.Sprintf("host.hostuuid=%s", props.GetIOC("host_hostuuid")),
		fmt.Sprintf("path=%s", path.Join(jail.Mountpoint, "root")),
		fmt.Sprintf("securelevel=%s", props.GetIOC("securelevel")),
		fmt.Sprintf("devfs_ruleset=%s", props.GetIOC("devfs_ruleset")),
		fmt.Sprintf("enforce_statfs=%s", props.GetIOC("enforce_statfs")),
		fmt.Sprintf("children.max=%s", props.GetIOC("children_max")),
		fmt.Sprintf("allow.set_hostname=%s", props.GetIOC("allow_set_hostname")),
		fmt.Sprintf("allow.sysvipc=%s", props.GetIOC("allow_sysvipc")),
		fmt.Sprintf("allow.chflags=%s", props.GetIOC("allow_chflags")),
		fmt.Sprintf("allow.mount=%s", props.GetIOC("allow_mount")),
		fmt.Sprintf("allow.mount.devfs=%s", props.GetIOC("allow_mount_devfs")),
		fmt.Sprintf("allow.mount.nullfs=%s", props.GetIOC("allow_mount_nullfs")),
		fmt.Sprintf("allow.mount.procfs=%s", props.GetIOC("allow_mount_procfs")),
		fmt.Sprintf("allow.mount.tmpfs=%s", props.GetIOC("allow_mount_tmpfs")),
		fmt.Sprintf("allow.mount.zfs=%s", props.GetIOC("allow_mount_zfs")),
		fmt.Sprintf("mount.fdescfs=%s", props.GetIOC("mount_fdescfs")),
		fmt.Sprintf("allow.quotas=%s", props.GetIOC("allow_quotas")),
		fmt.Sprintf("allow.socket_af=%s", props.GetIOC("allow_socket_af")),
		fmt.Sprintf("exec.prestart=%s", props.GetIOC("prestart")),
		fmt.Sprintf("exec.poststart=%s", props.GetIOC("poststart")),
		fmt.Sprintf("exec.prestop=%s", props.GetIOC("prestop")),
		fmt.Sprintf("exec.stop=%s", props.GetIOC("exec_stop")),
		fmt.Sprintf("exec.clean=%s", props.GetIOC("exec_clean")),
		fmt.Sprintf("exec.timeout=%s", props.GetIOC("exec_timeout")),
		fmt.Sprintf("exec.fib=%s", props.GetIOC("exec_fib")),
		fmt.Sprintf("stop.timeout=%s", props.GetIOC("stop_timeout")),
		fmt.Sprintf("mount.fstab=%s", path.Join(jail.Mountpoint, "fstab")),
		fmt.Sprintf("mount.devfs=%s", props.GetIOC("mount_devfs")),
		fmt.Sprintf("exec.consolelog=%s", logpath),
		"allow.dying",
		"persist",
	}
	gologit.Debugln(jailexec)
	out, err := exec.Command(jailexec[0], jailexec[1:]...).CombinedOutput()
	gologit.Debugln(string(out))
	if err != nil {
		gologit.Printf("%s\n", err)
	}

	// rctl_limits?
	// cpuset?

	// jail zfs
	if props.GetIOC("jail_zfs") == "on" {
		core.ZFSMust(
			fmt.Errorf("Error setting property"),
			"jail", fmt.Sprintf("ioc-%s", jail.HostUUID),
			path.Join(core.GetZFSRootPath(), props.GetIOC("jail_zfs_dataset")))
		out, err := exec.Command(
			"/usr/sbin/jexec",
			fmt.Sprintf("ioc-%s", jail.HostUUID),
			"zfs", "mount", "-a").CombinedOutput()
		gologit.Debugln(string(out))
		if err != nil {
			gologit.Printf("%s\n", err)
		}
	}

	// copy resolv conf
	err = core.CopyFile(
		"/etc/resolv.conf",
		path.Join(jail.Mountpoint, "root/etc/resolv.conf"))
	if err != nil {
		gologit.Printf("%s\n", err)
	}

	// start services
	fmt.Printf("  + Starting services\n")
	jexec := []string{}
	if props.GetIOC("exec_fib") != "0" {
		jexec = append(jexec, "/usr/sbin/setfib", props.GetIOC("exec_fib"))
	}

	jexec = append(jexec, "/usr/sbin/jexec")
	jexec = append(jexec, fmt.Sprintf("ioc-%s", jail.HostUUID))
	jexec = append(jexec, core.SplitFieldsQuoteSafe(props.GetIOC("exec_start"))...)
	out, err = exec.Command(jexec[0], jexec[1:]...).CombinedOutput()
	gologit.Debugln(string(out))
	if err != nil {
		gologit.Printf("%s\n", err)
	}

	// set last_started property
	t := time.Now()
	core.ZFSMust(
		fmt.Errorf("Error setting property"), "set",
		fmt.Sprintf(
			"org.freebsd.iocage:last_started=%s",
			t.Format("2006-01-02_15:04:05")),
		jail.Path)
}

func init() {
	RootCmd.AddCommand(&cobra.Command{
		Use:   "start UUID|TAG",
		Short: "start jail",
		Long:  "Start jail identified by UUID or TAG.",
		Run:   startCmdRun,
		PreRun: func(cmd *cobra.Command, args []string) {
			if len(args) == 0 {
				gologit.Fatalln("Required UUID|TAG not provided")
			}
		},
	})
}
<commit_msg>update<commit_after>package commands

import (
	"fmt"
	"os/exec"
	"path"
	"time"

	"rollcage/core"

	"github.com/cactus/cobra"
	"github.com/cactus/gologit"
)

func startCmdRun(cmd *cobra.Command, args []string) {
	// requires root
	if !core.IsRoot() {
		gologit.Fatalf("Must be root to stop\n")
	}

	jail, err := core.FindJail(args[0])
	if err != nil {
		gologit.Fatalf("No jail found by '%s'\n", args[0])
	}

	if jail.IsRunning() {
		gologit.Fatalf("Jail is already running!\n")
	}

	props := jail.GetProperties()

	// set a default path
	environ := []string{
		"PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin",
	}

	fmt.Printf("* Starting %s (%s)\n", jail.HostUUID, jail.Tag)
	// mount procfs
	if props.GetIOC("mount_procfs") == "1" {
		fmt.Printf("  + mounting procfs\n")
		procpath := path.Join(jail.Mountpoint, "root/proc")
		excmd := exec.Command("/sbin/mount", "-t", "procfs", "proc", procpath)
		excmd.Env = environ
		err := excmd.Run()
		if err != nil {
			gologit.Printf("%s\n", err)
		}
	}

	// prepare jail zfs dataset if enabled
	if props.GetIOC("jail_zfs") == "on" {
		fmt.Printf("  + jailing zfs dataset\n")
		setprops := core.ZFSProperties{
			"org.freebsd.iocage:allow_mount":     "1",
			"org.freebsd.iocage:allow_mount_zfs": "1",
			"org.freebsd.iocage:enforce_statfs":  "1",
		}
		jail.SetProperties(setprops)
		core.ZFSMust(
			fmt.Errorf("Error setting property"),
			"set", "jailed=on",
			path.Join(core.GetZFSRootPath(), props.GetIOC("jail_zfs_dataset")))
	}

	// bring up networking (vnet or legacy)
	ip4_addr_propline := "ip4.addr="
	ip6_addr_propline := "ip6.addr="
	if props.GetIOC("vnet") == "on" {
		fmt.Printf("  + Configuring VNET\n")
		// start VNET networking
	} else {
		// start standard networking (legacy?)
		if props.GetIOC("ip4_addr") != "none" {
			ip4_addr_propline += props.GetIOC("ip4_addr")
		}
		if props.GetIOC("ip6_addr") != "none" {
			ip6_addr_propline += props.GetIOC("ip6_addr")
		}
	}

	// get log dir
	logdir := core.ZFSMust(
		fmt.Errorf("Error setting property"),
		"get", "-H", "-o", "value", "mountpoint", core.GetZFSRootPath())
	logdir = path.Join(logdir, "log")

	logpath := path.Join(logdir, fmt.Sprintf("%s-console.log", jail.HostUUID))

	// start jail
	jailexec := []string{
		"/usr/sbin/jail", "-c",
		ip4_addr_propline,
		fmt.Sprintf("ip4.saddrsel=%s", props.GetIOC("ip4_saddrsel")),
		fmt.Sprintf("ip4=%s", props.GetIOC("ip4")),
		ip6_addr_propline,
		fmt.Sprintf("ip6.saddrsel=%s", props.GetIOC("ip6_saddrsel")),
		fmt.Sprintf("ip6=%s", props.GetIOC("ip6")),
		fmt.Sprintf("name=ioc-%s", jail.HostUUID),
		fmt.Sprintf("host.hostname=%s", props.GetIOC("hostname")),
		fmt.Sprintf("host.hostuuid=%s", props.GetIOC("host_hostuuid")),
		fmt.Sprintf("path=%s", path.Join(jail.Mountpoint, "root")),
		fmt.Sprintf("securelevel=%s", props.GetIOC("securelevel")),
		fmt.Sprintf("devfs_ruleset=%s", props.GetIOC("devfs_ruleset")),
		fmt.Sprintf("enforce_statfs=%s", props.GetIOC("enforce_statfs")),
		fmt.Sprintf("children.max=%s", props.GetIOC("children_max")),
		fmt.Sprintf("allow.set_hostname=%s", props.GetIOC("allow_set_hostname")),
		fmt.Sprintf("allow.sysvipc=%s", props.GetIOC("allow_sysvipc")),
		fmt.Sprintf("allow.chflags=%s", props.GetIOC("allow_chflags")),
		fmt.Sprintf("allow.mount=%s", props.GetIOC("allow_mount")),
		fmt.Sprintf("allow.mount.devfs=%s", props.GetIOC("allow_mount_devfs")),
		fmt.Sprintf("allow.mount.nullfs=%s", props.GetIOC("allow_mount_nullfs")),
		fmt.Sprintf("allow.mount.procfs=%s", props.GetIOC("allow_mount_procfs")),
		fmt.Sprintf("allow.mount.tmpfs=%s", props.GetIOC("allow_mount_tmpfs")),
		fmt.Sprintf("allow.mount.zfs=%s", props.GetIOC("allow_mount_zfs")),
		fmt.Sprintf("mount.fdescfs=%s", props.GetIOC("mount_fdescfs")),
		fmt.Sprintf("allow.quotas=%s", props.GetIOC("allow_quotas")),
		fmt.Sprintf("allow.socket_af=%s", props.GetIOC("allow_socket_af")),
		fmt.Sprintf("exec.prestart=%s", props.GetIOC("prestart")),
		fmt.Sprintf("exec.poststart=%s", props.GetIOC("poststart")),
		fmt.Sprintf("exec.prestop=%s", props.GetIOC("prestop")),
		fmt.Sprintf("exec.stop=%s", props.GetIOC("exec_stop")),
		fmt.Sprintf("exec.clean=%s", props.GetIOC("exec_clean")),
		fmt.Sprintf("exec.timeout=%s", props.GetIOC("exec_timeout")),
		fmt.Sprintf("exec.fib=%s", props.GetIOC("exec_fib")),
		fmt.Sprintf("stop.timeout=%s", props.GetIOC("stop_timeout")),
		fmt.Sprintf("mount.fstab=%s", path.Join(jail.Mountpoint, "fstab")),
		fmt.Sprintf("mount.devfs=%s", props.GetIOC("mount_devfs")),
		fmt.Sprintf("exec.consolelog=%s", logpath),
		"allow.dying",
		"persist",
	}
	gologit.Debugln(jailexec)
	out, err := exec.Command(jailexec[0], jailexec[1:]...).CombinedOutput()
	gologit.Debugln(string(out))
	if err != nil {
		gologit.Printf("%s\n", err)
	}

	// rctl_limits?
	// cpuset?

	// jail zfs
	if props.GetIOC("jail_zfs") == "on" {
		core.ZFSMust(
			fmt.Errorf("Error setting property"),
			"jail", fmt.Sprintf("ioc-%s", jail.HostUUID),
			path.Join(core.GetZFSRootPath(), props.GetIOC("jail_zfs_dataset")))
		out, err := exec.Command(
			"/usr/sbin/jexec",
			fmt.Sprintf("ioc-%s", jail.HostUUID),
			"zfs", "mount", "-a").CombinedOutput()
		gologit.Debugln(string(out))
		if err != nil {
			gologit.Printf("%s\n", err)
		}
	}

	// copy resolv conf
	err = core.CopyFile(
		"/etc/resolv.conf",
		path.Join(jail.Mountpoint, "root/etc/resolv.conf"))
	if err != nil {
		gologit.Printf("%s\n", err)
	}

	// start services
	fmt.Printf("  + Starting services\n")
	jexec := []string{}
	if props.GetIOC("exec_fib") != "0" {
		jexec = append(jexec, "/usr/sbin/setfib", props.GetIOC("exec_fib"))
	}

	jexec = append(jexec, "/usr/sbin/jexec")
	jexec = append(jexec, fmt.Sprintf("ioc-%s", jail.HostUUID))
	jexec = append(jexec, core.SplitFieldsQuoteSafe(props.GetIOC("exec_start"))...)
	out, err = exec.Command(jexec[0], jexec[1:]...).CombinedOutput()
	gologit.Debugln(string(out))
	if err != nil {
		gologit.Printf("%s\n", err)
	}

	// set last_started property
	t := time.Now()
	core.ZFSMust(
		fmt.Errorf("Error setting property"), "set",
		fmt.Sprintf(
			"org.freebsd.iocage:last_started=%s",
			t.Format("2006-01-02_15:04:05")),
		jail.Path)
}

func init() {
	RootCmd.AddCommand(&cobra.Command{
		Use:   "start UUID|TAG",
		Short: "start jail",
		Long:  "Start jail identified by UUID or TAG.",
		Run:   startCmdRun,
		PreRun: func(cmd *cobra.Command, args []string) {
			if len(args) == 0 {
				gologit.Fatalln("Required UUID|TAG not provided")
			}
		},
	})
}
<|endoftext|>


<commit_before>package sudoku

import (
	"fmt"
	"testing"
)

func TestSumdoku(t *testing.T) {
	t.Parallel()
	tests := []struct {
		start, solution []int
		solveable       bool
		chars           int
		constraints     []Constraint
	}{
		{
			make([]int, 81),
			[]int{
				6, 2, 9, 1, 4, 3, 8, 5, 7,
				3, 5, 7, 9, 6, 8, 4, 2, 1,
				1, 8, 4, 7, 5, 2, 6, 9, 3,
				2, 9, 5, 3, 1, 4, 7, 8, 6,
				7, 3, 6, 2, 8, 9, 1, 4, 5,
				8, 4, 1, 6, 7, 5, 2, 3, 9,
				9, 1, 8, 4, 3, 7, 5, 6, 2,
				4, 7, 3, 5, 2, 6, 9, 1, 8,
				5, 6, 2, 8, 9, 1, 3, 7, 4,
			},
			true,
			9,
			append(s9,
				UniqueSum{[]int{0, 1, 2, 11}, 24},
				UniqueSum{[]int{3, 4, 5}, 8},
				UniqueSum{[]int{6, 7, 8}, 20},
				UniqueSum{[]int{9, 10, 18}, 9},
				UniqueSum{[]int{12, 21}, 16},
				UniqueSum{[]int{13, 14, 15}, 18},
				UniqueSum{[]int{16, 17}, 3},
				UniqueSum{[]int{19, 20, 28, 29}, 26},
				UniqueSum{[]int{22, 30, 31}, 9},
				UniqueSum{[]int{23, 32, 40, 41}, 23},
				UniqueSum{[]int{24, 25, 33}, 22},
				UniqueSum{[]int{26, 34, 35}, 17},
				UniqueSum{[]int{27, 36, 45, 54}, 26},
				UniqueSum{[]int{37, 46, 55}, 8},
				UniqueSum{[]int{38, 39}, 8},
				UniqueSum{[]int{42, 43, 51, 60}, 12},
				UniqueSum{[]int{44, 53}, 14},
				UniqueSum{[]int{47, 48, 49, 50}, 19},
				UniqueSum{[]int{52, 61, 62}, 11},
				UniqueSum{[]int{56, 57}, 12},
				UniqueSum{[]int{58, 67, 75, 76}, 22},
				UniqueSum{[]int{59, 68, 69}, 22},
				UniqueSum{[]int{63, 72}, 9},
				UniqueSum{[]int{64, 73}, 13},
				UniqueSum{[]int{65, 66, 74}, 10},
				UniqueSum{[]int{70, 79}, 8},
				UniqueSum{[]int{71, 80}, 12},
				UniqueSum{[]int{77, 78}, 4},
			),
		},
		{
			make([]int, 81),
			[]int{
				2, 6, 7, 9, 5, 1, 3, 4, 8,
				3, 5, 4, 7, 2, 8, 1, 6, 9,
				8, 1, 9, 6, 3, 4, 2, 5, 7,
				9, 3, 6, 2, 4, 7, 5, 8, 1,
				5, 2, 8, 1, 9, 6, 7, 3, 4,
				4, 7, 1, 3, 8, 5, 9, 2, 6,
				1, 4, 5, 8, 7, 3, 6, 9, 2,
				6, 8, 2, 5, 1, 9, 4, 7, 3,
				7, 9, 3, 4, 6, 2, 8, 1, 5,
			},
			true,
			9,
			append(s9,
				UniqueSum{[]int{0, 9, 18, 27}, 22},
				UniqueSum{[]int{1, 2, 3, 10}, 27},
				UniqueSum{[]int{4, 5, 6}, 9},
				UniqueSum{[]int{7, 16}, 10},
				UniqueSum{[]int{8, 17}, 17},
				UniqueSum{[]int{11, 12, 21}, 17},
				UniqueSum{[]int{13, 22, 31}, 9},
				UniqueSum{[]int{14, 15, 23, 24}, 15},
				UniqueSum{[]int{19, 28}, 4},
				UniqueSum{[]int{20, 29, 38}, 23},
				UniqueSum{[]int{25, 26, 34}, 20},
				UniqueSum{[]int{30, 39}, 3},
				UniqueSum{[]int{32, 33}, 12},
				UniqueSum{[]int{35, 43, 44}, 8},
				UniqueSum{[]int{36, 37, 45, 54}, 12},
				UniqueSum{[]int{40, 41, 49}, 23},
				UniqueSum{[]int{42, 51, 60}, 22},
				UniqueSum{[]int{46, 55}, 11},
				UniqueSum{[]int{47, 48, 56}, 9},
				UniqueSum{[]int{50, 57, 58, 59}, 23},
				UniqueSum{[]int{52, 53, 61, 70}, 24},
				UniqueSum{[]int{62, 71, 79, 80}, 11},
				UniqueSum{[]int{63, 64, 72, 73}, 30},
				UniqueSum{[]int{65, 66, 67}, 8},
				UniqueSum{[]int{68, 69}, 13},
				UniqueSum{[]int{74, 75}, 7},
				UniqueSum{[]int{76, 77, 78}, 16},
			),
		},
		{
			make([]int, 81),
			[]int{
				3, 5, 4, 8, 1, 7, 2, 6, 9,
				8, 9, 2, 6, 3, 4, 7, 1, 5,
				1, 7, 6, 5, 9, 2, 8, 4, 3,
				9, 4, 8, 1, 2, 5, 6, 3, 7,
				7, 6, 5, 4, 8, 3, 1, 9, 2,
				2, 3, 1, 7, 6, 9, 4, 5, 8,
				6, 1, 9, 2, 5, 8, 3, 7, 4,
				4, 8, 3, 9, 7, 6, 5, 2, 1,
				5, 2, 7, 3, 4, 1, 9, 8, 6,
			},
			true,
			9,
			append(s9,
				UniqueSum{[]int{0, 1}, 8},
				UniqueSum{[]int{2, 9, 10, 11}, 23},
				UniqueSum{[]int{3, 12}, 14},
				UniqueSum{[]int{4, 13, 21, 22}, 18},
				UniqueSum{[]int{5, 6}, 9},
				UniqueSum{[]int{7, 8, 17, 26}, 23},
				UniqueSum{[]int{14, 15, 23, 24}, 21},
				UniqueSum{[]int{16, 25, 34}, 8},
				UniqueSum{[]int{18, 19}, 8},
				UniqueSum{[]int{20, 29, 30, 31}, 17},
				UniqueSum{[]int{27, 36}, 16},
				UniqueSum{[]int{28, 37, 38, 47}, 16},
				UniqueSum{[]int{32, 33}, 11},
				UniqueSum{[]int{35, 44}, 9},
				UniqueSum{[]int{39, 48}, 11},
				UniqueSum{[]int{40, 41, 42, 49}, 18},
				UniqueSum{[]int{43, 52, 53}, 22},
				UniqueSum{[]int{45, 54}, 8},
				UniqueSum{[]int{46, 55}, 4},
				UniqueSum{[]int{50, 57, 58, 59}, 24},
				UniqueSum{[]int{51, 60, 69}, 12},
				UniqueSum{[]int{56, 65}, 12},
				UniqueSum{[]int{61, 62}, 11},
				UniqueSum{[]int{63, 64, 72, 73}, 19},
				UniqueSum{[]int{66, 67, 68}, 22},
				UniqueSum{[]int{70, 71}, 3},
				UniqueSum{[]int{74, 75, 76, 77}, 15},
				UniqueSum{[]int{78, 79, 80}, 23},
			),
		},
	}
	for i, test := range tests {
		solved := Solve(test.start, test.chars, test.constraints)
		if test.solveable {
			if solved {
				for j, num := range test.start {
					if num != test.solution[j] {
						fmt.Println(test.start[:9])
						fmt.Println(test.start[9:18])
						fmt.Println(test.start[18:27])
						fmt.Println(test.start[27:36])
						fmt.Println(test.start[36:45])
						fmt.Println(test.start[45:54])
						fmt.Println(test.start[54:63])
						fmt.Println(test.start[63:72])
						fmt.Println(test.start[72:])
						t.Errorf("solution found does not match solution given for test %d", i+1)
						break
					}
				}
			} else {
				t.Errorf("didn't find solution in puzzle %d when solution expected", i+1)
			}
		} else {

		}
	}
}
<commit_msg>removed empty branch<commit_after>package sudoku

import (
	"fmt"
	"testing"
)

func TestSumdoku(t *testing.T) {
	t.Parallel()
	tests := []struct {
		start, solution []int
		solveable       bool
		chars           int
		constraints     []Constraint
	}{
		{
			make([]int, 81),
			[]int{
				6, 2, 9, 1, 4, 3, 8, 5, 7,
				3, 5, 7, 9, 6, 8, 4, 2, 1,
				1, 8, 4, 7, 5, 2, 6, 9, 3,
				2, 9, 5, 3, 1, 4, 7, 8, 6,
				7, 3, 6, 2, 8, 9, 1, 4, 5,
				8, 4, 1, 6, 7, 5, 2, 3, 9,
				9, 1, 8, 4, 3, 7, 5, 6, 2,
				4, 7, 3, 5, 2, 6, 9, 1, 8,
				5, 6, 2, 8, 9, 1, 3, 7, 4,
			},
			true,
			9,
			append(s9,
				UniqueSum{[]int{0, 1, 2, 11}, 24},
				UniqueSum{[]int{3, 4, 5}, 8},
				UniqueSum{[]int{6, 7, 8}, 20},
				UniqueSum{[]int{9, 10, 18}, 9},
				UniqueSum{[]int{12, 21}, 16},
				UniqueSum{[]int{13, 14, 15}, 18},
				UniqueSum{[]int{16, 17}, 3},
				UniqueSum{[]int{19, 20, 28, 29}, 26},
				UniqueSum{[]int{22, 30, 31}, 9},
				UniqueSum{[]int{23, 32, 40, 41}, 23},
				UniqueSum{[]int{24, 25, 33}, 22},
				UniqueSum{[]int{26, 34, 35}, 17},
				UniqueSum{[]int{27, 36, 45, 54}, 26},
				UniqueSum{[]int{37, 46, 55}, 8},
				UniqueSum{[]int{38, 39}, 8},
				UniqueSum{[]int{42, 43, 51, 60}, 12},
				UniqueSum{[]int{44, 53}, 14},
				UniqueSum{[]int{47, 48, 49, 50}, 19},
				UniqueSum{[]int{52, 61, 62}, 11},
				UniqueSum{[]int{56, 57}, 12},
				UniqueSum{[]int{58, 67, 75, 76}, 22},
				UniqueSum{[]int{59, 68, 69}, 22},
				UniqueSum{[]int{63, 72}, 9},
				UniqueSum{[]int{64, 73}, 13},
				UniqueSum{[]int{65, 66, 74}, 10},
				UniqueSum{[]int{70, 79}, 8},
				UniqueSum{[]int{71, 80}, 12},
				UniqueSum{[]int{77, 78}, 4},
			),
		},
		{
			make([]int, 81),
			[]int{
				2, 6, 7, 9, 5, 1, 3, 4, 8,
				3, 5, 4, 7, 2, 8, 1, 6, 9,
				8, 1, 9, 6, 3, 4, 2, 5, 7,
				9, 3, 6, 2, 4, 7, 5, 8, 1,
				5, 2, 8, 1, 9, 6, 7, 3, 4,
				4, 7, 1, 3, 8, 5, 9, 2, 6,
				1, 4, 5, 8, 7, 3, 6, 9, 2,
				6, 8, 2, 5, 1, 9, 4, 7, 3,
				7, 9, 3, 4, 6, 2, 8, 1, 5,
			},
			true,
			9,
			append(s9,
				UniqueSum{[]int{0, 9, 18, 27}, 22},
				UniqueSum{[]int{1, 2, 3, 10}, 27},
				UniqueSum{[]int{4, 5, 6}, 9},
				UniqueSum{[]int{7, 16}, 10},
				UniqueSum{[]int{8, 17}, 17},
				UniqueSum{[]int{11, 12, 21}, 17},
				UniqueSum{[]int{13, 22, 31}, 9},
				UniqueSum{[]int{14, 15, 23, 24}, 15},
				UniqueSum{[]int{19, 28}, 4},
				UniqueSum{[]int{20, 29, 38}, 23},
				UniqueSum{[]int{25, 26, 34}, 20},
				UniqueSum{[]int{30, 39}, 3},
				UniqueSum{[]int{32, 33}, 12},
				UniqueSum{[]int{35, 43, 44}, 8},
				UniqueSum{[]int{36, 37, 45, 54}, 12},
				UniqueSum{[]int{40, 41, 49}, 23},
				UniqueSum{[]int{42, 51, 60}, 22},
				UniqueSum{[]int{46, 55}, 11},
				UniqueSum{[]int{47, 48, 56}, 9},
				UniqueSum{[]int{50, 57, 58, 59}, 23},
				UniqueSum{[]int{52, 53, 61, 70}, 24},
				UniqueSum{[]int{62, 71, 79, 80}, 11},
				UniqueSum{[]int{63, 64, 72, 73}, 30},
				UniqueSum{[]int{65, 66, 67}, 8},
				UniqueSum{[]int{68, 69}, 13},
				UniqueSum{[]int{74, 75}, 7},
				UniqueSum{[]int{76, 77, 78}, 16},
			),
		},
		{
			make([]int, 81),
			[]int{
				3, 5, 4, 8, 1, 7, 2, 6, 9,
				8, 9, 2, 6, 3, 4, 7, 1, 5,
				1, 7, 6, 5, 9, 2, 8, 4, 3,
				9, 4, 8, 1, 2, 5, 6, 3, 7,
				7, 6, 5, 4, 8, 3, 1, 9, 2,
				2, 3, 1, 7, 6, 9, 4, 5, 8,
				6, 1, 9, 2, 5, 8, 3, 7, 4,
				4, 8, 3, 9, 7, 6, 5, 2, 1,
				5, 2, 7, 3, 4, 1, 9, 8, 6,
			},
			true,
			9,
			append(s9,
				UniqueSum{[]int{0, 1}, 8},
				UniqueSum{[]int{2, 9, 10, 11}, 23},
				UniqueSum{[]int{3, 12}, 14},
				UniqueSum{[]int{4, 13, 21, 22}, 18},
				UniqueSum{[]int{5, 6}, 9},
				UniqueSum{[]int{7, 8, 17, 26}, 23},
				UniqueSum{[]int{14, 15, 23, 24}, 21},
				UniqueSum{[]int{16, 25, 34}, 8},
				UniqueSum{[]int{18, 19}, 8},
				UniqueSum{[]int{20, 29, 30, 31}, 17},
				UniqueSum{[]int{27, 36}, 16},
				UniqueSum{[]int{28, 37, 38, 47}, 16},
				UniqueSum{[]int{32, 33}, 11},
				UniqueSum{[]int{35, 44}, 9},
				UniqueSum{[]int{39, 48}, 11},
				UniqueSum{[]int{40, 41, 42, 49}, 18},
				UniqueSum{[]int{43, 52, 53}, 22},
				UniqueSum{[]int{45, 54}, 8},
				UniqueSum{[]int{46, 55}, 4},
				UniqueSum{[]int{50, 57, 58, 59}, 24},
				UniqueSum{[]int{51, 60, 69}, 12},
				UniqueSum{[]int{56, 65}, 12},
				UniqueSum{[]int{61, 62}, 11},
				UniqueSum{[]int{63, 64, 72, 73}, 19},
				UniqueSum{[]int{66, 67, 68}, 22},
				UniqueSum{[]int{70, 71}, 3},
				UniqueSum{[]int{74, 75, 76, 77}, 15},
				UniqueSum{[]int{78, 79, 80}, 23},
			),
		},
	}
	for i, test := range tests {
		solved := Solve(test.start, test.chars, test.constraints)
		if test.solveable {
			if solved {
				for j, num := range test.start {
					if num != test.solution[j] {
						fmt.Println(test.start[:9])
						fmt.Println(test.start[9:18])
						fmt.Println(test.start[18:27])
						fmt.Println(test.start[27:36])
						fmt.Println(test.start[36:45])
						fmt.Println(test.start[45:54])
						fmt.Println(test.start[54:63])
						fmt.Println(test.start[63:72])
						fmt.Println(test.start[72:])
						t.Errorf("solution found does not match solution given for test %d", i+1)
						break
					}
				}
			} else {
				t.Errorf("didn't find solution in puzzle %d when solution expected", i+1)
			}
		}
	}
}
<|endoftext|>


<commit_before>package redblackbst

import (
	"bytes"
	"fmt"
	"reflect"
	"testing"
)

var tests = []struct {
	want graph
	prog P
}{
	{
		prog: P{Name: "simple put", Ops: []Op{
			{"Put", "b"},
			{"Put", "a"},
		}},
		want: makeGraph([]edge{
			{from: "b",
				to:    "a",
				dir:   "left",
				color: "red"},
		}),
	},
	{
		prog: P{Name: "simple put 2", Ops: []Op{
			{"Put", "a"},
			{"Put", "b"},
		}},
		want: makeGraph([]edge{
			{from: "b",
				to:    "a",
				dir:   "left",
				color: "red"},
		}),
	},
	{
		prog: P{Name: "two blacks", Ops: []Op{
			{"Put", "a"},
			{"Put", "e"},
			{"Put", "s"},
		}},
		want: makeGraph([]edge{
			{from: "e",
				to:    "a",
				dir:   "left",
				color: "black"},
			{from: "e",
				to:    "s",
				dir:   "right",
				color: "black"},
		}),
	},

	{
		prog: P{Name: "two blacks, one red", Ops: []Op{
			{"Put", "a"},
			{"Put", "e"},
			{"Put", "s"},
			{"Put", "r"},
		}},
		want: makeGraph([]edge{
			{from: "e",
				to:    "a",
				dir:   "left",
				color: "black"},
			{from: "e",
				to:    "s",
				dir:   "right",
				color: "black"},
			{from: "s",
				to:    "r",
				dir:   "left",
				color: "red"},
		}),
	},

	{
		prog: P{Name: "two blacks, two red", Ops: []Op{
			{"Put", "a"},
			{"Put", "e"},
			{"Put", "s"},
			{"Put", "r"},
			{"Put", "c"},
		}},
		want: makeGraph([]edge{
			{from: "e",
				to:    "c",
				dir:   "left",
				color: "black"},
			{from: "e",
				to:    "s",
				dir:   "right",
				color: "black"},
			{from: "s",
				to:    "r",
				dir:   "left",
				color: "red"},
			{from: "c",
				to:    "a",
				dir:   "left",
				color: "red"},
		}),
	},
}

type Op struct {
	Op  string
	Key string
}

type P struct {
	Name string
	Ops  []Op
}

func (p *P) Run(tree *RedBlack) {
	for step, op := range p.Ops {
		switch op.Op {
		case "Put":
			tree.Put(K(op.Key), struct{}{})
		case "Delete":
			tree.Delete(K(op.Key))
		case "Print":
			printTreeStats(tree, fmt.Sprintf("%s-%d", p.Name, step))
		}
	}
}

func TestScenarios(t *testing.T) {

	for _, tt := range tests {
		tree := New()
		tt.prog.Run(tree)

		want := tt.want
		got := toGraph(tree)

		if !reflect.DeepEqual(want.nodes, got.nodes) {
			openDot(want.toDot())
			openDot(got.toDot())
			t.Logf("want=%#v", want.nodes)
			t.Logf("got =%#v", got.nodes)
			t.Fatalf("%q: failed,", tt.prog.Name)
		}
	}
}

type nd struct {
	name  string
	edges []edge
}

type edge struct {
	from  string
	to    string
	dir   string
	color string
}

type graph struct {
	name  string
	nodes map[string]nd
}

func toGraph(tree *RedBlack) graph {
	g := graph{
		name:  "got",
		nodes: make(map[string]nd),
	}

	lo, _, _ := tree.Min()
	hi, _, _ := tree.Max()
	nodes(tree.root, func(n *node) bool {

		h := nd{
			name: string(n.key.(K)),
		}

		if n.left != nil {
			var leftColor string
			if isRed(n.left) {
				leftColor = "red"
			} else {
				leftColor = "black"
			}

			h.edges = append(h.edges, edge{
				from:  h.name,
				to:    string(n.left.key.(K)),
				color: leftColor,
				dir:   "left",
			})
		}
		if n.right != nil {
			var rightColor string
			if isRed(n.right) {
				rightColor = "red"
			} else {
				rightColor = "black"
			}
			h.edges = append(h.edges, edge{
				from:  h.name,
				to:    string(n.right.key.(K)),
				color: rightColor,
				dir:   "right",
			})
		}

		g.nodes[h.name] = h
		return true
	}, lo, hi)

	return g
}

func makeGraph(edges []edge) graph {
	g := graph{
		name:  "want",
		nodes: make(map[string]nd),
	}
	for _, e := range edges {
		n, ok := g.nodes[e.from]
		if !ok {
			n = nd{
				name:  e.from,
				edges: []edge{e},
			}
		} else {
			n.edges = append(n.edges, e)
		}
		g.nodes[e.from] = n
		n, ok = g.nodes[e.to]
		if !ok {
			n = nd{
				name: e.to,
			}
			g.nodes[e.to] = n
		}

	}
	return g
}

func (g graph) toDot() *bytes.Buffer {
	w := bytes.NewBuffer(nil)

	fmt.Fprintf(w, "digraph %q {", g.name)

	for _, node := range g.nodes {
		fmt.Fprintf(w, "\t%q [shape = circle];\n", node.name)
	}

	for _, node := range g.nodes {
		for _, edge := range node.edges {
			fmt.Fprintf(w, "\t%q -> %q [label=%q, color=%s];\n", node.name, edge.to, edge.dir, edge.color)
		}
	}

	fmt.Fprintf(w, "}\n")

	return w
}
<commit_msg>add more tests<commit_after>package redblackbst

import (
	"bytes"
	"fmt"
	"reflect"
	"testing"
)

var tests = []struct {
	want graph
	prog P
}{
	{
		prog: P{Name: "simple put", Ops: []Op{
			{"Put", "b"},
			{"Put", "a"},
		}},
		want: makeGraph([]edge{
			{from: "b",
				to:    "a",
				dir:   "left",
				color: "red"},
		}),
	},
	{
		prog: P{Name: "simple put 2", Ops: []Op{
			{"Put", "a"},
			{"Put", "b"},
		}},
		want: makeGraph([]edge{
			{from: "b",
				to:    "a",
				dir:   "left",
				color: "red"},
		}),
	},
	{
		prog: P{Name: "two blacks", Ops: []Op{
			{"Put", "a"},
			{"Put", "e"},
			{"Put", "s"},
		}},
		want: makeGraph([]edge{
			{from: "e",
				to:    "a",
				dir:   "left",
				color: "black"},
			{from: "e",
				to:    "s",
				dir:   "right",
				color: "black"},
		}),
	},

	{
		prog: P{Name: "two blacks, one red", Ops: []Op{
			{"Put", "a"},
			{"Put", "e"},
			{"Put", "s"},
			{"Put", "r"},
		}},
		want: makeGraph([]edge{
			{from: "e",
				to:    "a",
				dir:   "left",
				color: "black"},
			{from: "e",
				to:    "s",
				dir:   "right",
				color: "black"},
			{from: "s",
				to:    "r",
				dir:   "left",
				color: "red"},
		}),
	},

	{
		prog: P{Name: "two blacks, two red", Ops: []Op{
			{"Put", "a"},
			{"Put", "e"},
			{"Put", "s"},
			{"Put", "r"},
			{"Put", "c"},
		}},
		want: makeGraph([]edge{
			{from: "e",
				to:    "c",
				dir:   "left",
				color: "black"},
			{from: "e",
				to:    "s",
				dir:   "right",
				color: "black"},
			{from: "s",
				to:    "r",
				dir:   "left",
				color: "red"},
			{from: "c",
				to:    "a",
				dir:   "left",
				color: "red"},
		}),
	},
	{
		prog: P{Name: "two blacks, three red", Ops: []Op{
			{"Put", "a"},
			{"Put", "e"},
			{"Put", "s"},
			{"Put", "r"},
			{"Put", "c"},
			{"Put", "h"},
		}},
		want: makeGraph([]edge{
			{from: "r",
				to:    "e",
				dir:   "left",
				color: "red"},
			{from: "r",
				to:    "s",
				dir:   "right",
				color: "black"},
			{from: "e",
				to:    "c",
				dir:   "left",
				color: "black"},
			{from: "e",
				to:    "h",
				dir:   "right",
				color: "black"},
			{from: "c",
				to:    "a",
				dir:   "left",
				color: "red"},
		}),
	},

	{
		prog: P{Name: "standard indexing client", Ops: []Op{
			{"Put", "s"},
			{"Put", "e"},
			{"Put", "a"},
			{"Put", "r"},
			{"Put", "c"},
			{"Put", "h"},
			{"Put", "x"},
			{"Put", "m"},
			{"Put", "p"},
			{"Put", "l"},
		}},
		want: makeGraph([]edge{
			{from: "m",
				to:    "e",
				dir:   "left",
				color: "black"},
			{from: "m",
				to:    "r",
				dir:   "right",
				color: "black"},
			{from: "e",
				to:    "c",
				dir:   "left",
				color: "black"},
			{from: "e",
				to:    "l",
				dir:   "right",
				color: "black"},
			{from: "l",
				to:    "h",
				dir:   "left",
				color: "red"},
			{from: "c",
				to:    "a",
				dir:   "left",
				color: "red"},
			{from: "r",
				to:    "p",
				dir:   "left",
				color: "black"},
			{from: "r",
				to:    "x",
				dir:   "right",
				color: "black"},
			{from: "x",
				to:    "s",
				dir:   "left",
				color: "red"},
		}),
	},

	{
		prog: P{Name: "same keys, in increasing order", Ops: []Op{
			{"Put", "a"},
			{"Put", "c"},
			{"Put", "e"},
			{"Put", "h"},
			{"Put", "l"},
			{"Put", "m"},
			{"Put", "p"},
			{"Put", "r"},
			{"Put", "s"},
			{"Put", "x"},
		}},
		want: makeGraph([]edge{
			{from: "h",
				to:    "c",
				dir:   "left",
				color: "black"},
			{from: "h",
				to:    "r",
				dir:   "right",
				color: "black"},
			{from: "r",
				to:    "m",
				dir:   "left",
				color: "red"},
			{from: "r",
				to:    "x",
				dir:   "right",
				color: "black"},
			{from: "m",
				to:    "l",
				dir:   "left",
				color: "black"},
			{from: "m",
				to:    "p",
				dir:   "right",
				color: "black"},
			{from: "x",
				to:    "s",
				dir:   "left",
				color: "red"},
			{from: "c",
				to:    "a",
				dir:   "left",
				color: "black"},
			{from: "c",
				to:    "e",
				dir:   "right",
				color: "black"},
		}),
	},
}

type Op struct {
	Op  string
	Key string
}

type P struct {
	Name string
	Ops  []Op
}

func (p *P) Run(tree *RedBlack) {
	for step, op := range p.Ops {
		switch op.Op {
		case "Put":
			tree.Put(K(op.Key), struct{}{})
		case "Delete":
			tree.Delete(K(op.Key))
		case "Print":
			printTreeStats(tree, fmt.Sprintf("%s-%d", p.Name, step))
		}
	}
}

func TestScenarios(t *testing.T) {

	for _, tt := range tests {
		tree := New()
		tt.prog.Run(tree)

		want := tt.want
		got := toGraph(tree)

		if !reflect.DeepEqual(want.nodes, got.nodes) {
			openDot(want.toDot())
			openDot(got.toDot())
			t.Logf("want=%#v", want.nodes)
			t.Logf("got =%#v", got.nodes)
			t.Fatalf("%q: failed,", tt.prog.Name)
		}
	}
}

type nd struct {
	name  string
	edges []edge
}

type edge struct {
	from  string
	to    string
	dir   string
	color string
}

type graph struct {
	name  string
	nodes map[string]nd
}

func toGraph(tree *RedBlack) graph {
	g := graph{
		name:  "got",
		nodes: make(map[string]nd),
	}

	lo, _, _ := tree.Min()
	hi, _, _ := tree.Max()
	nodes(tree.root, func(n *node) bool {

		h := nd{
			name: string(n.key.(K)),
		}

		if n.left != nil {
			var leftColor string
			if isRed(n.left) {
				leftColor = "red"
			} else {
				leftColor = "black"
			}

			h.edges = append(h.edges, edge{
				from:  h.name,
				to:    string(n.left.key.(K)),
				color: leftColor,
				dir:   "left",
			})
		}
		if n.right != nil {
			var rightColor string
			if isRed(n.right) {
				rightColor = "red"
			} else {
				rightColor = "black"
			}
			h.edges = append(h.edges, edge{
				from:  h.name,
				to:    string(n.right.key.(K)),
				color: rightColor,
				dir:   "right",
			})
		}

		g.nodes[h.name] = h
		return true
	}, lo, hi)

	return g
}

func makeGraph(edges []edge) graph {
	g := graph{
		name:  "want",
		nodes: make(map[string]nd),
	}
	for _, e := range edges {
		n, ok := g.nodes[e.from]
		if !ok {
			n = nd{
				name:  e.from,
				edges: []edge{e},
			}
		} else {
			n.edges = append(n.edges, e)
		}
		g.nodes[e.from] = n
		n, ok = g.nodes[e.to]
		if !ok {
			n = nd{
				name: e.to,
			}
			g.nodes[e.to] = n
		}

	}
	return g
}

func (g graph) toDot() *bytes.Buffer {
	w := bytes.NewBuffer(nil)

	fmt.Fprintf(w, "digraph %q {", g.name)

	for _, node := range g.nodes {
		fmt.Fprintf(w, "\t%q [shape = circle];\n", node.name)
	}

	for _, node := range g.nodes {
		for _, edge := range node.edges {
			fmt.Fprintf(w, "\t%q -> %q [label=%q, color=%s];\n", node.name, edge.to, edge.dir, edge.color)
		}
	}

	fmt.Fprintf(w, "}\n")

	return w
}
<|endoftext|>


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

package mux

import (
	"fmt"
	"net/http"
	"path"

	"code.google.com/p/gorilla/context"
)

// NewRouter returns a new router instance.
func NewRouter() *Router {
	return &Router{namedRoutes: make(map[string]*Route)}
}

// Router registers routes to be matched and dispatches a handler.
//
// It implements the http.Handler interface, so it can be registered to serve
// requests:
//
//     var router = mux.NewRouter()
//
//     func main() {
//         http.Handle("/", router)
//     }
//
// Or, for Google App Engine, register it in a init() function:
//
//     func init() {
//         http.Handle("/", router)
//     }
//
// This will send all incoming requests to the router.
type Router struct {
	// Configurable Handler to be used when no route matches.
	NotFoundHandler http.Handler
	// Parent route, if this is a subrouter.
	parent parentRoute
	// Routes to be matched, in order.
	routes []*Route
	// Routes by name for URL building.
	namedRoutes map[string]*Route
	// See Route.strictSlash. This defines the default flag for new routes.
	strictSlash bool
}

// Match matches registered routes against the request.
func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
	return r.match(req, match)
}

// match matches registered routes against the request.
func (r *Router) match(req *http.Request, match *RouteMatch) bool {
	for _, route := range r.routes {
		if route.err == nil {
			if matched := route.match(req, match); matched {
				return true
			}
		}
	}
	return false
}

// ServeHTTP dispatches the handler registered in the matched route.
//
// When there is a match, the route variables can be retrieved calling
// mux.Vars(request).
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// Clean path to canonical form and redirect.
	if p := cleanPath(req.URL.Path); p != req.URL.Path {
		w.Header().Set("Location", p)
		w.WriteHeader(http.StatusMovedPermanently)
		return
	}
	var match RouteMatch
	var handler http.Handler
	if matched := r.Match(req, &match); matched {
		handler = match.Handler
		setVars(req, match.Vars)
		setCurrentRoute(req, match.Route)
	}
	if handler == nil {
		if r.NotFoundHandler == nil {
			r.NotFoundHandler = http.NotFoundHandler()
		}
		handler = r.NotFoundHandler
	}
	defer context.DefaultContext.Clear(req)
	handler.ServeHTTP(w, req)
}

// GetRoute returns a route registered with the given name.
func (r *Router) GetRoute(name string) *Route {
	if r.namedRoutes == nil {
		r.namedRoutes = make(map[string]*Route)
	}
	return r.namedRoutes[name]
}

// StrictSlash defines the slash behavior for new routes.
//
// When true, if the route path is /path/, accessing /path will redirect to
// /path/, and vice versa.
func (r *Router) StrictSlash(value bool) *Router {
	r.strictSlash = value
	return r
}

// getNamedRoutes returns the map where named routes are registered.
func (r *Router) getNamedRoutes() map[string]*Route {
	if r.namedRoutes == nil {
		if r.parent != nil {
			r.namedRoutes = r.parent.getNamedRoutes()
		} else {
			r.namedRoutes = make(map[string]*Route)
		}
	}
	return r.namedRoutes
}

// getRegexpGroup returns regexp definitions from the parent route, if any.
func (r *Router) getRegexpGroup() *routeRegexpGroup {
	if r.parent != nil {
		return r.parent.getRegexpGroup()
	}
	return nil
}

// ----------------------------------------------------------------------------
// Route factories
// ----------------------------------------------------------------------------

// NewRoute registers an empty route.
func (r *Router) NewRoute() *Route {
	route := &Route{parent: r, strictSlash: r.strictSlash}
	r.routes = append(r.routes, route)
	return route
}

// Handle registers a new route with a matcher for the URL path.
// See Route.Path.
func (r *Router) Handle(path string, handler http.Handler) *Route {
	return r.NewRoute().Path(path).Handler(handler)
}

// HandleFunc registers a new route with a matcher for the URL path.
// See Route.Path.
func (r *Router) HandleFunc(path string, f func(http.ResponseWriter,
	*http.Request),) *Route {
	return r.NewRoute().Path(path).HandlerFunc(f)
}

// Headers registers a new route with a matcher for request header values.
// See Route.Headers.
func (r *Router) Headers(pairs ...string) *Route {
	return r.NewRoute().Headers(pairs...)
}

// Host registers a new route with a matcher for the URL host.
// See Route.Host.
func (r *Router) Host(tpl string) *Route {
	return r.NewRoute().Host(tpl)
}

// MatcherFunc registers a new route with a custom matcher function.
// See Route.MatcherFunc.
func (r *Router) MatcherFunc(f MatcherFunc) *Route {
	return r.NewRoute().MatcherFunc(f)
}

// Methods registers a new route with a matcher for HTTP methods.
// See Route.Methods.
func (r *Router) Methods(methods ...string) *Route {
	return r.NewRoute().Methods(methods...)
}

// Path registers a new route with a matcher for the URL path.
// See Route.Path.
func (r *Router) Path(tpl string) *Route {
	return r.NewRoute().Path(tpl)
}

// PathPrefix registers a new route with a matcher for the URL path prefix.
// See Route.PathPrefix.
func (r *Router) PathPrefix(tpl string) *Route {
	return r.NewRoute().PathPrefix(tpl)
}

// Queries registers a new route with a matcher for URL query values.
// See Route.Queries.
func (r *Router) Queries(pairs ...string) *Route {
	return r.NewRoute().Queries(pairs...)
}

// Schemes registers a new route with a matcher for URL schemes.
// See Route.Schemes.
func (r *Router) Schemes(schemes ...string) *Route {
	return r.NewRoute().Schemes(schemes...)
}

// ----------------------------------------------------------------------------
// Context
// ----------------------------------------------------------------------------

// RouteMatch stores information about a matched route.
type RouteMatch struct {
	Route   *Route
	Handler http.Handler
	Vars    map[string]string
}

type contextKey int

const (
	varsKey contextKey = iota
	routeKey
)

// Vars returns the route variables for the current request, if any.
func Vars(r *http.Request) map[string]string {
	if rv := context.DefaultContext.Get(r, varsKey); rv != nil {
		return rv.(map[string]string)
	}
	return nil
}

// CurrentRoute returns the matched route for the current request, if any.
func CurrentRoute(r *http.Request) *Route {
	if rv := context.DefaultContext.Get(r, routeKey); rv != nil {
		return rv.(*Route)
	}
	return nil
}

func setVars(r *http.Request, val interface{}) {
	context.DefaultContext.Set(r, varsKey, val)
}

func setCurrentRoute(r *http.Request, val interface{}) {
	context.DefaultContext.Set(r, routeKey, val)
}

// ----------------------------------------------------------------------------
// Helpers
// ----------------------------------------------------------------------------

// cleanPath returns the canonical path for p, eliminating . and .. elements.
// Borrowed from the net/http package.
func cleanPath(p string) string {
	if p == "" {
		return "/"
	}
	if p[0] != '/' {
		p = "/" + p
	}
	np := path.Clean(p)
	// path.Clean removes trailing slash except for root;
	// put the trailing slash back if necessary.
	if p[len(p)-1] == '/' && np != "/" {
		np += "/"
	}
	return np
}

// uniqueVars returns an error if two slices contain duplicated strings.
func uniqueVars(s1, s2 []string) error {
	vars := make(map[string]bool)
	for _, s := range s1 {
		vars[s] = true
	}
	for _, s := range s2 {
		if vars[s] {
			return fmt.Errorf("mux: duplicated variable %q", s)
		}
	}
	return nil
}

// mapFromPairs converts variadic string parameters to a string map.
func mapFromPairs(pairs ...string) (map[string]string, error) {
	length := len(pairs)
	if length%2 != 0 {
		return nil, fmt.Errorf("mux: parameters must be multiple of 2, got %v",
			pairs)
	}
	m := make(map[string]string, length/2)
	for i := 0; i < length; i += 2 {
		m[pairs[i]] = pairs[i+1]
	}
	return m, nil
}

// matchInArray returns true if the given string value is in the array.
func matchInArray(arr []string, value string) bool {
	for _, v := range arr {
		if v == value {
			return true
		}
	}
	return false
}

// matchMap returns true if the given key/value pairs exist in a given map.
func matchMap(toCheck map[string]string, toMatch map[string][]string,
	canonicalKey bool) bool {
	for k, v := range toCheck {
		// Check if key exists.
		if canonicalKey {
			k = http.CanonicalHeaderKey(k)
		}
		if values := toMatch[k]; values == nil {
			return false
		} else if v != "" {
			// If value was defined as an empty string we only check that the
			// key exists. Otherwise we also check if the value exists.
			valueExists := false
			for _, value := range values {
				if v == value {
					valueExists = true
					break
				}
			}
			if !valueExists {
				return false
			}
		}
	}
	return true
}
<commit_msg>mux: go fmt.<commit_after>// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package mux

import (
	"fmt"
	"net/http"
	"path"

	"code.google.com/p/gorilla/context"
)

// NewRouter returns a new router instance.
func NewRouter() *Router {
	return &Router{namedRoutes: make(map[string]*Route)}
}

// Router registers routes to be matched and dispatches a handler.
//
// It implements the http.Handler interface, so it can be registered to serve
// requests:
//
//     var router = mux.NewRouter()
//
//     func main() {
//         http.Handle("/", router)
//     }
//
// Or, for Google App Engine, register it in a init() function:
//
//     func init() {
//         http.Handle("/", router)
//     }
//
// This will send all incoming requests to the router.
type Router struct {
	// Configurable Handler to be used when no route matches.
	NotFoundHandler http.Handler
	// Parent route, if this is a subrouter.
	parent parentRoute
	// Routes to be matched, in order.
	routes []*Route
	// Routes by name for URL building.
	namedRoutes map[string]*Route
	// See Route.strictSlash. This defines the default flag for new routes.
	strictSlash bool
}

// Match matches registered routes against the request.
func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
	return r.match(req, match)
}

// match matches registered routes against the request.
func (r *Router) match(req *http.Request, match *RouteMatch) bool {
	for _, route := range r.routes {
		if route.err == nil {
			if matched := route.match(req, match); matched {
				return true
			}
		}
	}
	return false
}

// ServeHTTP dispatches the handler registered in the matched route.
//
// When there is a match, the route variables can be retrieved calling
// mux.Vars(request).
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// Clean path to canonical form and redirect.
	if p := cleanPath(req.URL.Path); p != req.URL.Path {
		w.Header().Set("Location", p)
		w.WriteHeader(http.StatusMovedPermanently)
		return
	}
	var match RouteMatch
	var handler http.Handler
	if matched := r.Match(req, &match); matched {
		handler = match.Handler
		setVars(req, match.Vars)
		setCurrentRoute(req, match.Route)
	}
	if handler == nil {
		if r.NotFoundHandler == nil {
			r.NotFoundHandler = http.NotFoundHandler()
		}
		handler = r.NotFoundHandler
	}
	defer context.DefaultContext.Clear(req)
	handler.ServeHTTP(w, req)
}

// GetRoute returns a route registered with the given name.
func (r *Router) GetRoute(name string) *Route {
	if r.namedRoutes == nil {
		r.namedRoutes = make(map[string]*Route)
	}
	return r.namedRoutes[name]
}

// StrictSlash defines the slash behavior for new routes.
//
// When true, if the route path is /path/, accessing /path will redirect to
// /path/, and vice versa.
func (r *Router) StrictSlash(value bool) *Router {
	r.strictSlash = value
	return r
}

// ----------------------------------------------------------------------------
// parentRoute
// ----------------------------------------------------------------------------

// getNamedRoutes returns the map where named routes are registered.
func (r *Router) getNamedRoutes() map[string]*Route {
	if r.namedRoutes == nil {
		if r.parent != nil {
			r.namedRoutes = r.parent.getNamedRoutes()
		} else {
			r.namedRoutes = make(map[string]*Route)
		}
	}
	return r.namedRoutes
}

// getRegexpGroup returns regexp definitions from the parent route, if any.
func (r *Router) getRegexpGroup() *routeRegexpGroup {
	if r.parent != nil {
		return r.parent.getRegexpGroup()
	}
	return nil
}

// ----------------------------------------------------------------------------
// Route factories
// ----------------------------------------------------------------------------

// NewRoute registers an empty route.
func (r *Router) NewRoute() *Route {
	route := &Route{parent: r, strictSlash: r.strictSlash}
	r.routes = append(r.routes, route)
	return route
}

// Handle registers a new route with a matcher for the URL path.
// See Route.Path.
func (r *Router) Handle(path string, handler http.Handler) *Route {
	return r.NewRoute().Path(path).Handler(handler)
}

// HandleFunc registers a new route with a matcher for the URL path.
// See Route.Path.
func (r *Router) HandleFunc(path string, f func(http.ResponseWriter,
	*http.Request),) *Route {
	return r.NewRoute().Path(path).HandlerFunc(f)
}

// Headers registers a new route with a matcher for request header values.
// See Route.Headers.
func (r *Router) Headers(pairs ...string) *Route {
	return r.NewRoute().Headers(pairs...)
}

// Host registers a new route with a matcher for the URL host.
// See Route.Host.
func (r *Router) Host(tpl string) *Route {
	return r.NewRoute().Host(tpl)
}

// MatcherFunc registers a new route with a custom matcher function.
// See Route.MatcherFunc.
func (r *Router) MatcherFunc(f MatcherFunc) *Route {
	return r.NewRoute().MatcherFunc(f)
}

// Methods registers a new route with a matcher for HTTP methods.
// See Route.Methods.
func (r *Router) Methods(methods ...string) *Route {
	return r.NewRoute().Methods(methods...)
}

// Path registers a new route with a matcher for the URL path.
// See Route.Path.
func (r *Router) Path(tpl string) *Route {
	return r.NewRoute().Path(tpl)
}

// PathPrefix registers a new route with a matcher for the URL path prefix.
// See Route.PathPrefix.
func (r *Router) PathPrefix(tpl string) *Route {
	return r.NewRoute().PathPrefix(tpl)
}

// Queries registers a new route with a matcher for URL query values.
// See Route.Queries.
func (r *Router) Queries(pairs ...string) *Route {
	return r.NewRoute().Queries(pairs...)
}

// Schemes registers a new route with a matcher for URL schemes.
// See Route.Schemes.
func (r *Router) Schemes(schemes ...string) *Route {
	return r.NewRoute().Schemes(schemes...)
}

// ----------------------------------------------------------------------------
// Context
// ----------------------------------------------------------------------------

// RouteMatch stores information about a matched route.
type RouteMatch struct {
	Route   *Route
	Handler http.Handler
	Vars    map[string]string
}

type contextKey int

const (
	varsKey contextKey = iota
	routeKey
)

// Vars returns the route variables for the current request, if any.
func Vars(r *http.Request) map[string]string {
	if rv := context.DefaultContext.Get(r, varsKey); rv != nil {
		return rv.(map[string]string)
	}
	return nil
}

// CurrentRoute returns the matched route for the current request, if any.
func CurrentRoute(r *http.Request) *Route {
	if rv := context.DefaultContext.Get(r, routeKey); rv != nil {
		return rv.(*Route)
	}
	return nil
}

func setVars(r *http.Request, val interface{}) {
	context.DefaultContext.Set(r, varsKey, val)
}

func setCurrentRoute(r *http.Request, val interface{}) {
	context.DefaultContext.Set(r, routeKey, val)
}

// ----------------------------------------------------------------------------
// Helpers
// ----------------------------------------------------------------------------

// cleanPath returns the canonical path for p, eliminating . and .. elements.
// Borrowed from the net/http package.
func cleanPath(p string) string {
	if p == "" {
		return "/"
	}
	if p[0] != '/' {
		p = "/" + p
	}
	np := path.Clean(p)
	// path.Clean removes trailing slash except for root;
	// put the trailing slash back if necessary.
	if p[len(p)-1] == '/' && np != "/" {
		np += "/"
	}
	return np
}

// uniqueVars returns an error if two slices contain duplicated strings.
func uniqueVars(s1, s2 []string) error {
	vars := make(map[string]bool)
	for _, s := range s1 {
		vars[s] = true
	}
	for _, s := range s2 {
		if vars[s] {
			return fmt.Errorf("mux: duplicated variable %q", s)
		}
	}
	return nil
}

// mapFromPairs converts variadic string parameters to a string map.
func mapFromPairs(pairs ...string) (map[string]string, error) {
	length := len(pairs)
	if length%2 != 0 {
		return nil, fmt.Errorf("mux: parameters must be multiple of 2, got %v",
			pairs)
	}
	m := make(map[string]string, length/2)
	for i := 0; i < length; i += 2 {
		m[pairs[i]] = pairs[i+1]
	}
	return m, nil
}

// matchInArray returns true if the given string value is in the array.
func matchInArray(arr []string, value string) bool {
	for _, v := range arr {
		if v == value {
			return true
		}
	}
	return false
}

// matchMap returns true if the given key/value pairs exist in a given map.
func matchMap(toCheck map[string]string, toMatch map[string][]string,
	canonicalKey bool) bool {
	for k, v := range toCheck {
		// Check if key exists.
		if canonicalKey {
			k = http.CanonicalHeaderKey(k)
		}
		if values := toMatch[k]; values == nil {
			return false
		} else if v != "" {
			// If value was defined as an empty string we only check that the
			// key exists. Otherwise we also check if the value exists.
			valueExists := false
			for _, value := range values {
				if v == value {
					valueExists = true
					break
				}
			}
			if !valueExists {
				return false
			}
		}
	}
	return true
}
<|endoftext|>