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

package task

import (
	"context"
	"encoding/json"
	"fmt"
	"sync"
	"time"

	"github.com/goharbor/harbor/src/jobservice/job"
	"github.com/goharbor/harbor/src/lib/errors"
	"github.com/goharbor/harbor/src/lib/log"
	"github.com/goharbor/harbor/src/lib/orm"
	"github.com/goharbor/harbor/src/lib/q"
	"github.com/goharbor/harbor/src/pkg/task/dao"
)

var (
	// ExecMgr is a global execution manager instance
	ExecMgr                            = NewExecutionManager()
	executionSweeperCount              = map[string]uint8{}
	defaultExecutionSweeperCount uint8 = 50
)

// ExecutionManager manages executions.
// The execution and task managers provide an execution-task model to abstract the interactive with jobservice.
// All of the operations with jobservice should be delegated by them
type ExecutionManager interface {
	// Create an execution. The "vendorType" specifies the type of vendor (e.g. replication, scan, gc, retention, etc.),
	// and the "vendorID" specifies the ID of vendor if needed(e.g. policy ID for replication and retention).
	// The "extraAttrs" can be used to set the customized attributes
	Create(ctx context.Context, vendorType string, vendorID int64, trigger string,
		extraAttrs ...map[string]interface{}) (id int64, err error)
	// Update the extra attributes of the specified execution
	UpdateExtraAttrs(ctx context.Context, id int64, extraAttrs map[string]interface{}) (err error)
	// MarkDone marks the status of the specified execution as success.
	// It must be called to update the execution status if the created execution contains no tasks.
	// In other cases, the execution status can be calculated from the referenced tasks automatically
	// and no need to update it explicitly
	MarkDone(ctx context.Context, id int64, message string) (err error)
	// MarkError marks the status of the specified execution as error.
	// It must be called to update the execution status when failed to create tasks.
	// In other cases, the execution status can be calculated from the referenced tasks automatically
	// and no need to update it explicitly
	MarkError(ctx context.Context, id int64, message string) (err error)
	// Stop all linked tasks of the specified execution
	Stop(ctx context.Context, id int64) (err error)
	// StopAndWait stops all linked tasks of the specified execution and waits until all tasks are stopped
	// or get an error
	StopAndWait(ctx context.Context, id int64, timeout time.Duration) (err error)
	// Delete the specified execution and its tasks
	Delete(ctx context.Context, id int64) (err error)
	// Get the specified execution
	Get(ctx context.Context, id int64) (execution *Execution, err error)
	// List executions according to the query
	// Query the "ExtraAttrs" by setting 'query.Keywords["ExtraAttrs.key"]="value"'
	List(ctx context.Context, query *q.Query) (executions []*Execution, err error)
	// Count counts total of executions according to the query.
	// Query the "ExtraAttrs" by setting 'query.Keywords["ExtraAttrs.key"]="value"'
	Count(ctx context.Context, query *q.Query) (int64, error)
}

// NewExecutionManager return an instance of the default execution manager
func NewExecutionManager() ExecutionManager {
	return &executionManager{
		executionDAO: dao.NewExecutionDAO(),
		taskMgr:      Mgr,
		taskDAO:      dao.NewTaskDAO(),
		ormCreator:   orm.Crt,
	}
}

type executionManager struct {
	executionDAO dao.ExecutionDAO
	taskMgr      Manager
	taskDAO      dao.TaskDAO
	ormCreator   orm.Creator
}

func (e *executionManager) Count(ctx context.Context, query *q.Query) (int64, error) {
	return e.executionDAO.Count(ctx, query)
}

func (e *executionManager) Create(ctx context.Context, vendorType string, vendorID int64, trigger string,
	extraAttrs ...map[string]interface{}) (int64, error) {
	extras := map[string]interface{}{}
	if len(extraAttrs) > 0 && extraAttrs[0] != nil {
		extras = extraAttrs[0]
	}
	data, err := json.Marshal(extras)
	if err != nil {
		return 0, err
	}

	now := time.Now()
	execution := &dao.Execution{
		VendorType: vendorType,
		VendorID:   vendorID,
		Status:     job.RunningStatus.String(),
		Trigger:    trigger,
		ExtraAttrs: string(data),
		StartTime:  now,
		UpdateTime: now,
	}
	id, err := e.executionDAO.Create(ctx, execution)
	if err != nil {
		return 0, err
	}

	// sweep the execution records to avoid the execution/task records explosion
	go func() {
		// as we start a new transaction here to do the sweep work, the current execution record
		// may be not visible(when the transaction in which the current execution is created
		// in isn't committed), this will cause that there are one more execution records than expected
		ctx := orm.NewContext(context.Background(), e.ormCreator.Create())
		if err := e.sweep(ctx, vendorType, vendorID); err != nil {
			log.Errorf("failed to sweep the executions of %s: %v", vendorType, err)
			return
		}
	}()

	return id, nil
}

func (e *executionManager) sweep(ctx context.Context, vendorType string, vendorID int64) error {
	count := executionSweeperCount[vendorType]
	if count == 0 {
		count = defaultExecutionSweeperCount
	}
	for {
		// the function "List" of the execution manager returns the execution records
		// ordered by start time. After the sorting is supported in query, we should
		// specify the sorting explicitly
		// the execution records in second page are always the candidates should to be swept
		executions, err := e.List(ctx, &q.Query{
			Keywords: map[string]interface{}{
				"VendorType": vendorType,
				"VendorID":   vendorID,
			},
			PageNumber: 2,
			PageSize:   int64(count),
		})
		if err != nil {
			return err
		}
		// no execution records need to be swept, return directly
		if len(executions) == 0 {
			return nil
		}
		for _, execution := range executions {
			// if the status of the execution isn't final, skip
			if !job.Status(execution.Status).Final() {
				continue
			}
			if err = e.Delete(ctx, execution.ID); err != nil {
				log.Errorf("failed to delete the execution %d: %v", execution.ID, err)
				continue
			}
		}
	}
}

func (e *executionManager) UpdateExtraAttrs(ctx context.Context, id int64, extraAttrs map[string]interface{}) error {
	data, err := json.Marshal(extraAttrs)
	if err != nil {
		return err
	}

	execution := &dao.Execution{
		ID:         id,
		ExtraAttrs: string(data),
		UpdateTime: time.Now(),
	}

	return e.executionDAO.Update(ctx, execution, "ExtraAttrs", "UpdateTime")
}

func (e *executionManager) MarkDone(ctx context.Context, id int64, message string) error {
	now := time.Now()
	return e.executionDAO.Update(ctx, &dao.Execution{
		ID:            id,
		Status:        job.SuccessStatus.String(),
		StatusMessage: message,
		UpdateTime:    now,
		EndTime:       now,
	}, "Status", "StatusMessage", "UpdateTime", "EndTime")
}

func (e *executionManager) MarkError(ctx context.Context, id int64, message string) error {
	now := time.Now()
	return e.executionDAO.Update(ctx, &dao.Execution{
		ID:            id,
		Status:        job.ErrorStatus.String(),
		StatusMessage: message,
		UpdateTime:    now,
		EndTime:       now,
	}, "Status", "StatusMessage", "UpdateTime", "EndTime")
}

func (e *executionManager) Stop(ctx context.Context, id int64) error {
	execution, err := e.executionDAO.Get(ctx, id)
	if err != nil {
		return err
	}

	// when an execution is in final status, if it contains task that is a periodic or retrying job it will
	// run again in the near future, so we must operate the stop action
	tasks, err := e.taskDAO.List(ctx, &q.Query{
		Keywords: map[string]interface{}{
			"ExecutionID": id,
		},
	})
	if err != nil {
		return err
	}
	// contains no task and the status isn't final, update the status to stop directly
	if len(tasks) == 0 && !job.Status(execution.Status).Final() {
		now := time.Now()
		return e.executionDAO.Update(ctx, &dao.Execution{
			ID:         id,
			Status:     job.StoppedStatus.String(),
			UpdateTime: now,
			EndTime:    now,
		}, "Status", "UpdateTime", "EndTime")
	}

	for _, task := range tasks {
		if err = e.taskMgr.Stop(ctx, task.ID); err != nil {
			log.Errorf("failed to stop task %d: %v", task.ID, err)
			continue
		}
	}

	// refresh the status explicitly in case that the execution status
	// isn't refreshed by task status change hook
	_, _, err = e.executionDAO.RefreshStatus(ctx, id)
	return err
}

func (e *executionManager) StopAndWait(ctx context.Context, id int64, timeout time.Duration) error {
	var (
		overtime bool
		errChan  = make(chan error)
		lock     = sync.RWMutex{}
	)
	go func() {
		// stop the execution
		if err := e.Stop(ctx, id); err != nil {
			errChan <- err
			return
		}
		// check the status of the execution
		interval := 100 * time.Millisecond
		stop := false
		for !stop {
			execution, err := e.executionDAO.Get(ctx, id)
			if err != nil {
				errChan <- err
				return
			}
			// if the status is final, return
			if job.Status(execution.Status).Final() {
				errChan <- nil
				return
			}
			time.Sleep(interval)
			if interval < 1*time.Second {
				interval = interval * 2
			}
			lock.RLock()
			stop = overtime
			lock.RUnlock()
		}
	}()

	select {
	case <-time.After(timeout):
		lock.Lock()
		overtime = true
		lock.Unlock()
		return fmt.Errorf("stopping the execution %d timeout", id)
	case err := <-errChan:
		return err
	}
}

func (e *executionManager) Delete(ctx context.Context, id int64) error {
	tasks, err := e.taskDAO.List(ctx, &q.Query{
		Keywords: map[string]interface{}{
			"ExecutionID": id,
		},
	})
	if err != nil {
		return err
	}

	for _, task := range tasks {
		if !job.Status(task.Status).Final() {
			return errors.New(nil).WithCode(errors.PreconditionCode).
				WithMessage("the execution %d has tasks that aren't in final status, stop the tasks first", id)
		}
		if err = e.taskDAO.Delete(ctx, task.ID); err != nil {
			return err
		}
	}

	return e.executionDAO.Delete(ctx, id)
}

func (e *executionManager) Get(ctx context.Context, id int64) (*Execution, error) {
	execution, err := e.executionDAO.Get(ctx, id)
	if err != nil {
		return nil, err
	}
	return e.populateExecution(ctx, execution), nil
}

func (e *executionManager) List(ctx context.Context, query *q.Query) ([]*Execution, error) {
	executions, err := e.executionDAO.List(ctx, query)
	if err != nil {
		return nil, err
	}
	var execs []*Execution
	for _, execution := range executions {
		execs = append(execs, e.populateExecution(ctx, execution))
	}
	return execs, nil
}

func (e *executionManager) populateExecution(ctx context.Context, execution *dao.Execution) *Execution {
	exec := &Execution{
		ID:            execution.ID,
		VendorType:    execution.VendorType,
		VendorID:      execution.VendorID,
		Status:        execution.Status,
		StatusMessage: execution.StatusMessage,
		Metrics:       nil,
		Trigger:       execution.Trigger,
		StartTime:     execution.StartTime,
		UpdateTime:    execution.UpdateTime,
		EndTime:       execution.EndTime,
	}

	if len(execution.ExtraAttrs) > 0 {
		extras := map[string]interface{}{}
		if err := json.Unmarshal([]byte(execution.ExtraAttrs), &extras); err != nil {
			log.Errorf("failed to unmarshal the extra attributes of execution %d: %v", execution.ID, err)
		} else {
			exec.ExtraAttrs = extras
		}
	}

	// populate task metrics
	metrics, err := e.executionDAO.GetMetrics(ctx, execution.ID)
	if err != nil {
		log.Errorf("failed to get metrics of the execution %d: %v", execution.ID, err)
	} else {
		exec.Metrics = metrics
	}

	return exec
}

// SetExecutionSweeperCount sets the count of execution records retained by the sweeper
// If no count is set for the specified vendor, the default value will be used
// The sweeper retains the latest created #count execution records for the specified vendor
func SetExecutionSweeperCount(vendorType string, count uint8) {
	executionSweeperCount[vendorType] = count
}
<commit_msg>Ignore the not found error in some cases<commit_after>// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package task

import (
	"context"
	"encoding/json"
	"fmt"
	"sync"
	"time"

	"github.com/goharbor/harbor/src/jobservice/job"
	"github.com/goharbor/harbor/src/lib/errors"
	"github.com/goharbor/harbor/src/lib/log"
	"github.com/goharbor/harbor/src/lib/orm"
	"github.com/goharbor/harbor/src/lib/q"
	"github.com/goharbor/harbor/src/pkg/task/dao"
)

var (
	// ExecMgr is a global execution manager instance
	ExecMgr                            = NewExecutionManager()
	executionSweeperCount              = map[string]uint8{}
	defaultExecutionSweeperCount uint8 = 50
)

// ExecutionManager manages executions.
// The execution and task managers provide an execution-task model to abstract the interactive with jobservice.
// All of the operations with jobservice should be delegated by them
type ExecutionManager interface {
	// Create an execution. The "vendorType" specifies the type of vendor (e.g. replication, scan, gc, retention, etc.),
	// and the "vendorID" specifies the ID of vendor if needed(e.g. policy ID for replication and retention).
	// The "extraAttrs" can be used to set the customized attributes
	Create(ctx context.Context, vendorType string, vendorID int64, trigger string,
		extraAttrs ...map[string]interface{}) (id int64, err error)
	// Update the extra attributes of the specified execution
	UpdateExtraAttrs(ctx context.Context, id int64, extraAttrs map[string]interface{}) (err error)
	// MarkDone marks the status of the specified execution as success.
	// It must be called to update the execution status if the created execution contains no tasks.
	// In other cases, the execution status can be calculated from the referenced tasks automatically
	// and no need to update it explicitly
	MarkDone(ctx context.Context, id int64, message string) (err error)
	// MarkError marks the status of the specified execution as error.
	// It must be called to update the execution status when failed to create tasks.
	// In other cases, the execution status can be calculated from the referenced tasks automatically
	// and no need to update it explicitly
	MarkError(ctx context.Context, id int64, message string) (err error)
	// Stop all linked tasks of the specified execution
	Stop(ctx context.Context, id int64) (err error)
	// StopAndWait stops all linked tasks of the specified execution and waits until all tasks are stopped
	// or get an error
	StopAndWait(ctx context.Context, id int64, timeout time.Duration) (err error)
	// Delete the specified execution and its tasks
	Delete(ctx context.Context, id int64) (err error)
	// Get the specified execution
	Get(ctx context.Context, id int64) (execution *Execution, err error)
	// List executions according to the query
	// Query the "ExtraAttrs" by setting 'query.Keywords["ExtraAttrs.key"]="value"'
	List(ctx context.Context, query *q.Query) (executions []*Execution, err error)
	// Count counts total of executions according to the query.
	// Query the "ExtraAttrs" by setting 'query.Keywords["ExtraAttrs.key"]="value"'
	Count(ctx context.Context, query *q.Query) (int64, error)
}

// NewExecutionManager return an instance of the default execution manager
func NewExecutionManager() ExecutionManager {
	return &executionManager{
		executionDAO: dao.NewExecutionDAO(),
		taskMgr:      Mgr,
		taskDAO:      dao.NewTaskDAO(),
		ormCreator:   orm.Crt,
	}
}

type executionManager struct {
	executionDAO dao.ExecutionDAO
	taskMgr      Manager
	taskDAO      dao.TaskDAO
	ormCreator   orm.Creator
}

func (e *executionManager) Count(ctx context.Context, query *q.Query) (int64, error) {
	return e.executionDAO.Count(ctx, query)
}

func (e *executionManager) Create(ctx context.Context, vendorType string, vendorID int64, trigger string,
	extraAttrs ...map[string]interface{}) (int64, error) {
	extras := map[string]interface{}{}
	if len(extraAttrs) > 0 && extraAttrs[0] != nil {
		extras = extraAttrs[0]
	}
	data, err := json.Marshal(extras)
	if err != nil {
		return 0, err
	}

	now := time.Now()
	execution := &dao.Execution{
		VendorType: vendorType,
		VendorID:   vendorID,
		Status:     job.RunningStatus.String(),
		Trigger:    trigger,
		ExtraAttrs: string(data),
		StartTime:  now,
		UpdateTime: now,
	}
	id, err := e.executionDAO.Create(ctx, execution)
	if err != nil {
		return 0, err
	}

	// sweep the execution records to avoid the execution/task records explosion
	go func() {
		// as we start a new transaction here to do the sweep work, the current execution record
		// may be not visible(when the transaction in which the current execution is created
		// in isn't committed), this will cause that there are one more execution records than expected
		ctx := orm.NewContext(context.Background(), e.ormCreator.Create())
		if err := e.sweep(ctx, vendorType, vendorID); err != nil {
			log.Errorf("failed to sweep the executions of %s: %v", vendorType, err)
			return
		}
	}()

	return id, nil
}

func (e *executionManager) sweep(ctx context.Context, vendorType string, vendorID int64) error {
	count := executionSweeperCount[vendorType]
	if count == 0 {
		count = defaultExecutionSweeperCount
	}
	for {
		// the function "List" of the execution manager returns the execution records
		// ordered by start time. After the sorting is supported in query, we should
		// specify the sorting explicitly
		// the execution records in second page are always the candidates should to be swept
		executions, err := e.List(ctx, &q.Query{
			Keywords: map[string]interface{}{
				"VendorType": vendorType,
				"VendorID":   vendorID,
			},
			PageNumber: 2,
			PageSize:   int64(count),
		})
		if err != nil {
			return err
		}
		// no execution records need to be swept, return directly
		if len(executions) == 0 {
			return nil
		}
		for _, execution := range executions {
			// if the status of the execution isn't final, skip
			if !job.Status(execution.Status).Final() {
				continue
			}
			if err = e.Delete(ctx, execution.ID); err != nil {
				// the execution may be deleted by the other sweep operation, ignore the not found error
				if errors.IsNotFoundErr(err) {
					continue
				}
				log.Errorf("failed to delete the execution %d: %v", execution.ID, err)
			}
		}
	}
}

func (e *executionManager) UpdateExtraAttrs(ctx context.Context, id int64, extraAttrs map[string]interface{}) error {
	data, err := json.Marshal(extraAttrs)
	if err != nil {
		return err
	}

	execution := &dao.Execution{
		ID:         id,
		ExtraAttrs: string(data),
		UpdateTime: time.Now(),
	}

	return e.executionDAO.Update(ctx, execution, "ExtraAttrs", "UpdateTime")
}

func (e *executionManager) MarkDone(ctx context.Context, id int64, message string) error {
	now := time.Now()
	return e.executionDAO.Update(ctx, &dao.Execution{
		ID:            id,
		Status:        job.SuccessStatus.String(),
		StatusMessage: message,
		UpdateTime:    now,
		EndTime:       now,
	}, "Status", "StatusMessage", "UpdateTime", "EndTime")
}

func (e *executionManager) MarkError(ctx context.Context, id int64, message string) error {
	now := time.Now()
	return e.executionDAO.Update(ctx, &dao.Execution{
		ID:            id,
		Status:        job.ErrorStatus.String(),
		StatusMessage: message,
		UpdateTime:    now,
		EndTime:       now,
	}, "Status", "StatusMessage", "UpdateTime", "EndTime")
}

func (e *executionManager) Stop(ctx context.Context, id int64) error {
	execution, err := e.executionDAO.Get(ctx, id)
	if err != nil {
		return err
	}

	// when an execution is in final status, if it contains task that is a periodic or retrying job it will
	// run again in the near future, so we must operate the stop action
	tasks, err := e.taskDAO.List(ctx, &q.Query{
		Keywords: map[string]interface{}{
			"ExecutionID": id,
		},
	})
	if err != nil {
		return err
	}
	// contains no task and the status isn't final, update the status to stop directly
	if len(tasks) == 0 && !job.Status(execution.Status).Final() {
		now := time.Now()
		return e.executionDAO.Update(ctx, &dao.Execution{
			ID:         id,
			Status:     job.StoppedStatus.String(),
			UpdateTime: now,
			EndTime:    now,
		}, "Status", "UpdateTime", "EndTime")
	}

	for _, task := range tasks {
		if err = e.taskMgr.Stop(ctx, task.ID); err != nil {
			log.Errorf("failed to stop task %d: %v", task.ID, err)
			continue
		}
	}

	// refresh the status explicitly in case that the execution status
	// isn't refreshed by task status change hook
	_, _, err = e.executionDAO.RefreshStatus(ctx, id)
	return err
}

func (e *executionManager) StopAndWait(ctx context.Context, id int64, timeout time.Duration) error {
	var (
		overtime bool
		errChan  = make(chan error)
		lock     = sync.RWMutex{}
	)
	go func() {
		// stop the execution
		if err := e.Stop(ctx, id); err != nil {
			errChan <- err
			return
		}
		// check the status of the execution
		interval := 100 * time.Millisecond
		stop := false
		for !stop {
			execution, err := e.executionDAO.Get(ctx, id)
			if err != nil {
				errChan <- err
				return
			}
			// if the status is final, return
			if job.Status(execution.Status).Final() {
				errChan <- nil
				return
			}
			time.Sleep(interval)
			if interval < 1*time.Second {
				interval = interval * 2
			}
			lock.RLock()
			stop = overtime
			lock.RUnlock()
		}
	}()

	select {
	case <-time.After(timeout):
		lock.Lock()
		overtime = true
		lock.Unlock()
		return fmt.Errorf("stopping the execution %d timeout", id)
	case err := <-errChan:
		return err
	}
}

func (e *executionManager) Delete(ctx context.Context, id int64) error {
	tasks, err := e.taskDAO.List(ctx, &q.Query{
		Keywords: map[string]interface{}{
			"ExecutionID": id,
		},
	})
	if err != nil {
		return err
	}

	for _, task := range tasks {
		if !job.Status(task.Status).Final() {
			return errors.New(nil).WithCode(errors.PreconditionCode).
				WithMessage("the execution %d has tasks that aren't in final status, stop the tasks first", id)
		}
		if err = e.taskDAO.Delete(ctx, task.ID); err != nil {
			// the tasks may be deleted by the other execution deletion operation in the same time(e.g. execution sweeper),
			// ignore the not found error for the tasks
			if errors.IsNotFoundErr(err) {
				continue
			}
			return err
		}
	}

	return e.executionDAO.Delete(ctx, id)
}

func (e *executionManager) Get(ctx context.Context, id int64) (*Execution, error) {
	execution, err := e.executionDAO.Get(ctx, id)
	if err != nil {
		return nil, err
	}
	return e.populateExecution(ctx, execution), nil
}

func (e *executionManager) List(ctx context.Context, query *q.Query) ([]*Execution, error) {
	executions, err := e.executionDAO.List(ctx, query)
	if err != nil {
		return nil, err
	}
	var execs []*Execution
	for _, execution := range executions {
		execs = append(execs, e.populateExecution(ctx, execution))
	}
	return execs, nil
}

func (e *executionManager) populateExecution(ctx context.Context, execution *dao.Execution) *Execution {
	exec := &Execution{
		ID:            execution.ID,
		VendorType:    execution.VendorType,
		VendorID:      execution.VendorID,
		Status:        execution.Status,
		StatusMessage: execution.StatusMessage,
		Metrics:       nil,
		Trigger:       execution.Trigger,
		StartTime:     execution.StartTime,
		UpdateTime:    execution.UpdateTime,
		EndTime:       execution.EndTime,
	}

	if len(execution.ExtraAttrs) > 0 {
		extras := map[string]interface{}{}
		if err := json.Unmarshal([]byte(execution.ExtraAttrs), &extras); err != nil {
			log.Errorf("failed to unmarshal the extra attributes of execution %d: %v", execution.ID, err)
		} else {
			exec.ExtraAttrs = extras
		}
	}

	// populate task metrics
	metrics, err := e.executionDAO.GetMetrics(ctx, execution.ID)
	if err != nil {
		log.Errorf("failed to get metrics of the execution %d: %v", execution.ID, err)
	} else {
		exec.Metrics = metrics
	}

	return exec
}

// SetExecutionSweeperCount sets the count of execution records retained by the sweeper
// If no count is set for the specified vendor, the default value will be used
// The sweeper retains the latest created #count execution records for the specified vendor
func SetExecutionSweeperCount(vendorType string, count uint8) {
	executionSweeperCount[vendorType] = count
}
<|endoftext|>


<commit_before>// Copyright 2009 The Go 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 unicode provides data and functions to test some properties of
// Unicode code points.
package unicode

const (
	MaxRune         = '\U0010FFFF' // Maximum valid Unicode code point.
	ReplacementChar = '\uFFFD'     // Represents invalid code points.
	MaxASCII        = '\u007F'     // maximum ASCII value.
	MaxLatin1       = '\u00FF'     // maximum Latin-1 value.
)

// RangeTable defines a set of Unicode code points by listing the ranges of
// code points within the set. The ranges are listed in two slices
// to save space: a slice of 16-bit ranges and a slice of 32-bit ranges.
// The two slices must be in sorted order and non-overlapping.
// Also, R32 should contain only values >= 0x10000 (1<<16).
type RangeTable struct {
	R16         []Range16
	R32         []Range32
	LatinOffset int // number of entries in R16 with Hi <= MaxLatin1
}

// Range16 represents of a range of 16-bit Unicode code points.  The range runs from Lo to Hi
// inclusive and has the specified stride.
type Range16 struct {
	Lo     uint16
	Hi     uint16
	Stride uint16
}

// Range32 represents of a range of Unicode code points and is used when one or
// more of the values will not fit in 16 bits.  The range runs from Lo to Hi
// inclusive and has the specified stride. Lo and Hi must always be >= 1<<16.
type Range32 struct {
	Lo     uint32
	Hi     uint32
	Stride uint32
}

// CaseRange represents a range of Unicode code points for simple (one
// code point to one code point) case conversion.
// The range runs from Lo to Hi inclusive, with a fixed stride of 1.  Deltas
// are the number to add to the code point to reach the code point for a
// different case for that character.  They may be negative.  If zero, it
// means the character is in the corresponding case. There is a special
// case representing sequences of alternating corresponding Upper and Lower
// pairs.  It appears with a fixed Delta of
//	{UpperLower, UpperLower, UpperLower}
// The constant UpperLower has an otherwise impossible delta value.
type CaseRange struct {
	Lo    uint32
	Hi    uint32
	Delta d
}

// SpecialCase represents language-specific case mappings such as Turkish.
// Methods of SpecialCase customize (by overriding) the standard mappings.
type SpecialCase []CaseRange

// BUG(r): There is no mechanism for full case folding, that is, for
// characters that involve multiple runes in the input or output.

// Indices into the Delta arrays inside CaseRanges for case mapping.
const (
	UpperCase = iota
	LowerCase
	TitleCase
	MaxCase
)

type d [MaxCase]rune // to make the CaseRanges text shorter

// If the Delta field of a CaseRange is UpperLower or LowerUpper, it means
// this CaseRange represents a sequence of the form (say)
// Upper Lower Upper Lower.
const (
	UpperLower = MaxRune + 1 // (Cannot be a valid delta.)
)

// linearMax is the maximum size table for linear search for non-Latin1 rune.
// Derived by running 'go test -calibrate'.
const linearMax = 18

// is16 reports whether r is in the sorted slice of 16-bit ranges.
func is16(ranges []Range16, r uint16) bool {
	if len(ranges) <= linearMax || r <= MaxLatin1 {
		for i := range ranges {
			range_ := &ranges[i]
			if r < range_.Lo {
				return false
			}
			if r <= range_.Hi {
				return (r-range_.Lo)%range_.Stride == 0
			}
		}
		return false
	}

	// binary search over ranges
	lo := 0
	hi := len(ranges)
	for lo < hi {
		m := lo + (hi-lo)/2
		range_ := &ranges[m]
		if range_.Lo <= r && r <= range_.Hi {
			return (r-range_.Lo)%range_.Stride == 0
		}
		if r < range_.Lo {
			hi = m
		} else {
			lo = m + 1
		}
	}
	return false
}

// is32 reports whether r is in the sorted slice of 32-bit ranges.
func is32(ranges []Range32, r uint32) bool {
	if len(ranges) <= linearMax {
		for i := range ranges {
			range_ := &ranges[i]
			if r < range_.Lo {
				return false
			}
			if r <= range_.Hi {
				return (r-range_.Lo)%range_.Stride == 0
			}
		}
		return false
	}

	// binary search over ranges
	lo := 0
	hi := len(ranges)
	for lo < hi {
		m := lo + (hi-lo)/2
		range_ := ranges[m]
		if range_.Lo <= r && r <= range_.Hi {
			return (r-range_.Lo)%range_.Stride == 0
		}
		if r < range_.Lo {
			hi = m
		} else {
			lo = m + 1
		}
	}
	return false
}

// Is reports whether the rune is in the specified table of ranges.
func Is(rangeTab *RangeTable, r rune) bool {
	r16 := rangeTab.R16
	if len(r16) > 0 && r <= rune(r16[len(r16)-1].Hi) {
		return is16(r16, uint16(r))
	}
	r32 := rangeTab.R32
	if len(r32) > 0 && r >= rune(r32[0].Lo) {
		return is32(r32, uint32(r))
	}
	return false
}

func isExcludingLatin(rangeTab *RangeTable, r rune) bool {
	r16 := rangeTab.R16
	if off := rangeTab.LatinOffset; len(r16) > off && r <= rune(r16[len(r16)-1].Hi) {
		return is16(r16[off:], uint16(r))
	}
	r32 := rangeTab.R32
	if len(r32) > 0 && r >= rune(r32[0].Lo) {
		return is32(r32, uint32(r))
	}
	return false
}

// IsUpper reports whether the rune is an upper case letter.
func IsUpper(r rune) bool {
	// See comment in IsGraphic.
	if uint32(r) <= MaxLatin1 {
		return properties[uint8(r)]&pLmask == pLu
	}
	return isExcludingLatin(Upper, r)
}

// IsLower reports whether the rune is a lower case letter.
func IsLower(r rune) bool {
	// See comment in IsGraphic.
	if uint32(r) <= MaxLatin1 {
		return properties[uint8(r)]&pLmask == pLl
	}
	return isExcludingLatin(Lower, r)
}

// IsTitle reports whether the rune is a title case letter.
func IsTitle(r rune) bool {
	if r <= MaxLatin1 {
		return false
	}
	return isExcludingLatin(Title, r)
}

// to maps the rune using the specified case mapping.
func to(_case int, r rune, caseRange []CaseRange) rune {
	if _case < 0 || MaxCase <= _case {
		return ReplacementChar // as reasonable an error as any
	}
	// binary search over ranges
	lo := 0
	hi := len(caseRange)
	for lo < hi {
		m := lo + (hi-lo)/2
		cr := caseRange[m]
		if rune(cr.Lo) <= r && r <= rune(cr.Hi) {
			delta := rune(cr.Delta[_case])
			if delta > MaxRune {
				// In an Upper-Lower sequence, which always starts with
				// an UpperCase letter, the real deltas always look like:
				//	{0, 1, 0}    UpperCase (Lower is next)
				//	{-1, 0, -1}  LowerCase (Upper, Title are previous)
				// The characters at even offsets from the beginning of the
				// sequence are upper case; the ones at odd offsets are lower.
				// The correct mapping can be done by clearing or setting the low
				// bit in the sequence offset.
				// The constants UpperCase and TitleCase are even while LowerCase
				// is odd so we take the low bit from _case.
				return rune(cr.Lo) + ((r-rune(cr.Lo))&^1 | rune(_case&1))
			}
			return r + delta
		}
		if r < rune(cr.Lo) {
			hi = m
		} else {
			lo = m + 1
		}
	}
	return r
}

// To maps the rune to the specified case: UpperCase, LowerCase, or TitleCase.
func To(_case int, r rune) rune {
	return to(_case, r, CaseRanges)
}

// ToUpper maps the rune to upper case.
func ToUpper(r rune) rune {
	if r <= MaxASCII {
		if 'a' <= r && r <= 'z' {
			r -= 'a' - 'A'
		}
		return r
	}
	return To(UpperCase, r)
}

// ToLower maps the rune to lower case.
func ToLower(r rune) rune {
	if r <= MaxASCII {
		if 'A' <= r && r <= 'Z' {
			r += 'a' - 'A'
		}
		return r
	}
	return To(LowerCase, r)
}

// ToTitle maps the rune to title case.
func ToTitle(r rune) rune {
	if r <= MaxASCII {
		if 'a' <= r && r <= 'z' { // title case is upper case for ASCII
			r -= 'a' - 'A'
		}
		return r
	}
	return To(TitleCase, r)
}

// ToUpper maps the rune to upper case giving priority to the special mapping.
func (special SpecialCase) ToUpper(r rune) rune {
	r1 := to(UpperCase, r, []CaseRange(special))
	if r1 == r {
		r1 = ToUpper(r)
	}
	return r1
}

// ToTitle maps the rune to title case giving priority to the special mapping.
func (special SpecialCase) ToTitle(r rune) rune {
	r1 := to(TitleCase, r, []CaseRange(special))
	if r1 == r {
		r1 = ToTitle(r)
	}
	return r1
}

// ToLower maps the rune to lower case giving priority to the special mapping.
func (special SpecialCase) ToLower(r rune) rune {
	r1 := to(LowerCase, r, []CaseRange(special))
	if r1 == r {
		r1 = ToLower(r)
	}
	return r1
}

// caseOrbit is defined in tables.go as []foldPair.  Right now all the
// entries fit in uint16, so use uint16.  If that changes, compilation
// will fail (the constants in the composite literal will not fit in uint16)
// and the types here can change to uint32.
type foldPair struct {
	From uint16
	To   uint16
}

// SimpleFold iterates over Unicode code points equivalent under
// the Unicode-defined simple case folding.  Among the code points
// equivalent to rune (including rune itself), SimpleFold returns the
// smallest rune >= r if one exists, or else the smallest rune >= 0.
//
// For example:
//	SimpleFold('A') = 'a'
//	SimpleFold('a') = 'A'
//
//	SimpleFold('K') = 'k'
//	SimpleFold('k') = '\u212A' (Kelvin symbol, K)
//	SimpleFold('\u212A') = 'K'
//
//	SimpleFold('1') = '1'
//
func SimpleFold(r rune) rune {
	// Consult caseOrbit table for special cases.
	lo := 0
	hi := len(caseOrbit)
	for lo < hi {
		m := lo + (hi-lo)/2
		if rune(caseOrbit[m].From) < r {
			lo = m + 1
		} else {
			hi = m
		}
	}
	if lo < len(caseOrbit) && rune(caseOrbit[lo].From) == r {
		return rune(caseOrbit[lo].To)
	}

	// No folding specified.  This is a one- or two-element
	// equivalence class containing rune and ToLower(rune)
	// and ToUpper(rune) if they are different from rune.
	if l := ToLower(r); l != r {
		return l
	}
	return ToUpper(r)
}
<commit_msg>unicode: delete appearance of unused LowerUpper term from comment If a LowerUpper ever happens, maketables will complain.<commit_after>// Copyright 2009 The Go 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 unicode provides data and functions to test some properties of
// Unicode code points.
package unicode

const (
	MaxRune         = '\U0010FFFF' // Maximum valid Unicode code point.
	ReplacementChar = '\uFFFD'     // Represents invalid code points.
	MaxASCII        = '\u007F'     // maximum ASCII value.
	MaxLatin1       = '\u00FF'     // maximum Latin-1 value.
)

// RangeTable defines a set of Unicode code points by listing the ranges of
// code points within the set. The ranges are listed in two slices
// to save space: a slice of 16-bit ranges and a slice of 32-bit ranges.
// The two slices must be in sorted order and non-overlapping.
// Also, R32 should contain only values >= 0x10000 (1<<16).
type RangeTable struct {
	R16         []Range16
	R32         []Range32
	LatinOffset int // number of entries in R16 with Hi <= MaxLatin1
}

// Range16 represents of a range of 16-bit Unicode code points.  The range runs from Lo to Hi
// inclusive and has the specified stride.
type Range16 struct {
	Lo     uint16
	Hi     uint16
	Stride uint16
}

// Range32 represents of a range of Unicode code points and is used when one or
// more of the values will not fit in 16 bits.  The range runs from Lo to Hi
// inclusive and has the specified stride. Lo and Hi must always be >= 1<<16.
type Range32 struct {
	Lo     uint32
	Hi     uint32
	Stride uint32
}

// CaseRange represents a range of Unicode code points for simple (one
// code point to one code point) case conversion.
// The range runs from Lo to Hi inclusive, with a fixed stride of 1.  Deltas
// are the number to add to the code point to reach the code point for a
// different case for that character.  They may be negative.  If zero, it
// means the character is in the corresponding case. There is a special
// case representing sequences of alternating corresponding Upper and Lower
// pairs.  It appears with a fixed Delta of
//	{UpperLower, UpperLower, UpperLower}
// The constant UpperLower has an otherwise impossible delta value.
type CaseRange struct {
	Lo    uint32
	Hi    uint32
	Delta d
}

// SpecialCase represents language-specific case mappings such as Turkish.
// Methods of SpecialCase customize (by overriding) the standard mappings.
type SpecialCase []CaseRange

// BUG(r): There is no mechanism for full case folding, that is, for
// characters that involve multiple runes in the input or output.

// Indices into the Delta arrays inside CaseRanges for case mapping.
const (
	UpperCase = iota
	LowerCase
	TitleCase
	MaxCase
)

type d [MaxCase]rune // to make the CaseRanges text shorter

// If the Delta field of a CaseRange is UpperLower, it means
// this CaseRange represents a sequence of the form (say)
// Upper Lower Upper Lower.
const (
	UpperLower = MaxRune + 1 // (Cannot be a valid delta.)
)

// linearMax is the maximum size table for linear search for non-Latin1 rune.
// Derived by running 'go test -calibrate'.
const linearMax = 18

// is16 reports whether r is in the sorted slice of 16-bit ranges.
func is16(ranges []Range16, r uint16) bool {
	if len(ranges) <= linearMax || r <= MaxLatin1 {
		for i := range ranges {
			range_ := &ranges[i]
			if r < range_.Lo {
				return false
			}
			if r <= range_.Hi {
				return (r-range_.Lo)%range_.Stride == 0
			}
		}
		return false
	}

	// binary search over ranges
	lo := 0
	hi := len(ranges)
	for lo < hi {
		m := lo + (hi-lo)/2
		range_ := &ranges[m]
		if range_.Lo <= r && r <= range_.Hi {
			return (r-range_.Lo)%range_.Stride == 0
		}
		if r < range_.Lo {
			hi = m
		} else {
			lo = m + 1
		}
	}
	return false
}

// is32 reports whether r is in the sorted slice of 32-bit ranges.
func is32(ranges []Range32, r uint32) bool {
	if len(ranges) <= linearMax {
		for i := range ranges {
			range_ := &ranges[i]
			if r < range_.Lo {
				return false
			}
			if r <= range_.Hi {
				return (r-range_.Lo)%range_.Stride == 0
			}
		}
		return false
	}

	// binary search over ranges
	lo := 0
	hi := len(ranges)
	for lo < hi {
		m := lo + (hi-lo)/2
		range_ := ranges[m]
		if range_.Lo <= r && r <= range_.Hi {
			return (r-range_.Lo)%range_.Stride == 0
		}
		if r < range_.Lo {
			hi = m
		} else {
			lo = m + 1
		}
	}
	return false
}

// Is reports whether the rune is in the specified table of ranges.
func Is(rangeTab *RangeTable, r rune) bool {
	r16 := rangeTab.R16
	if len(r16) > 0 && r <= rune(r16[len(r16)-1].Hi) {
		return is16(r16, uint16(r))
	}
	r32 := rangeTab.R32
	if len(r32) > 0 && r >= rune(r32[0].Lo) {
		return is32(r32, uint32(r))
	}
	return false
}

func isExcludingLatin(rangeTab *RangeTable, r rune) bool {
	r16 := rangeTab.R16
	if off := rangeTab.LatinOffset; len(r16) > off && r <= rune(r16[len(r16)-1].Hi) {
		return is16(r16[off:], uint16(r))
	}
	r32 := rangeTab.R32
	if len(r32) > 0 && r >= rune(r32[0].Lo) {
		return is32(r32, uint32(r))
	}
	return false
}

// IsUpper reports whether the rune is an upper case letter.
func IsUpper(r rune) bool {
	// See comment in IsGraphic.
	if uint32(r) <= MaxLatin1 {
		return properties[uint8(r)]&pLmask == pLu
	}
	return isExcludingLatin(Upper, r)
}

// IsLower reports whether the rune is a lower case letter.
func IsLower(r rune) bool {
	// See comment in IsGraphic.
	if uint32(r) <= MaxLatin1 {
		return properties[uint8(r)]&pLmask == pLl
	}
	return isExcludingLatin(Lower, r)
}

// IsTitle reports whether the rune is a title case letter.
func IsTitle(r rune) bool {
	if r <= MaxLatin1 {
		return false
	}
	return isExcludingLatin(Title, r)
}

// to maps the rune using the specified case mapping.
func to(_case int, r rune, caseRange []CaseRange) rune {
	if _case < 0 || MaxCase <= _case {
		return ReplacementChar // as reasonable an error as any
	}
	// binary search over ranges
	lo := 0
	hi := len(caseRange)
	for lo < hi {
		m := lo + (hi-lo)/2
		cr := caseRange[m]
		if rune(cr.Lo) <= r && r <= rune(cr.Hi) {
			delta := rune(cr.Delta[_case])
			if delta > MaxRune {
				// In an Upper-Lower sequence, which always starts with
				// an UpperCase letter, the real deltas always look like:
				//	{0, 1, 0}    UpperCase (Lower is next)
				//	{-1, 0, -1}  LowerCase (Upper, Title are previous)
				// The characters at even offsets from the beginning of the
				// sequence are upper case; the ones at odd offsets are lower.
				// The correct mapping can be done by clearing or setting the low
				// bit in the sequence offset.
				// The constants UpperCase and TitleCase are even while LowerCase
				// is odd so we take the low bit from _case.
				return rune(cr.Lo) + ((r-rune(cr.Lo))&^1 | rune(_case&1))
			}
			return r + delta
		}
		if r < rune(cr.Lo) {
			hi = m
		} else {
			lo = m + 1
		}
	}
	return r
}

// To maps the rune to the specified case: UpperCase, LowerCase, or TitleCase.
func To(_case int, r rune) rune {
	return to(_case, r, CaseRanges)
}

// ToUpper maps the rune to upper case.
func ToUpper(r rune) rune {
	if r <= MaxASCII {
		if 'a' <= r && r <= 'z' {
			r -= 'a' - 'A'
		}
		return r
	}
	return To(UpperCase, r)
}

// ToLower maps the rune to lower case.
func ToLower(r rune) rune {
	if r <= MaxASCII {
		if 'A' <= r && r <= 'Z' {
			r += 'a' - 'A'
		}
		return r
	}
	return To(LowerCase, r)
}

// ToTitle maps the rune to title case.
func ToTitle(r rune) rune {
	if r <= MaxASCII {
		if 'a' <= r && r <= 'z' { // title case is upper case for ASCII
			r -= 'a' - 'A'
		}
		return r
	}
	return To(TitleCase, r)
}

// ToUpper maps the rune to upper case giving priority to the special mapping.
func (special SpecialCase) ToUpper(r rune) rune {
	r1 := to(UpperCase, r, []CaseRange(special))
	if r1 == r {
		r1 = ToUpper(r)
	}
	return r1
}

// ToTitle maps the rune to title case giving priority to the special mapping.
func (special SpecialCase) ToTitle(r rune) rune {
	r1 := to(TitleCase, r, []CaseRange(special))
	if r1 == r {
		r1 = ToTitle(r)
	}
	return r1
}

// ToLower maps the rune to lower case giving priority to the special mapping.
func (special SpecialCase) ToLower(r rune) rune {
	r1 := to(LowerCase, r, []CaseRange(special))
	if r1 == r {
		r1 = ToLower(r)
	}
	return r1
}

// caseOrbit is defined in tables.go as []foldPair.  Right now all the
// entries fit in uint16, so use uint16.  If that changes, compilation
// will fail (the constants in the composite literal will not fit in uint16)
// and the types here can change to uint32.
type foldPair struct {
	From uint16
	To   uint16
}

// SimpleFold iterates over Unicode code points equivalent under
// the Unicode-defined simple case folding.  Among the code points
// equivalent to rune (including rune itself), SimpleFold returns the
// smallest rune >= r if one exists, or else the smallest rune >= 0.
//
// For example:
//	SimpleFold('A') = 'a'
//	SimpleFold('a') = 'A'
//
//	SimpleFold('K') = 'k'
//	SimpleFold('k') = '\u212A' (Kelvin symbol, K)
//	SimpleFold('\u212A') = 'K'
//
//	SimpleFold('1') = '1'
//
func SimpleFold(r rune) rune {
	// Consult caseOrbit table for special cases.
	lo := 0
	hi := len(caseOrbit)
	for lo < hi {
		m := lo + (hi-lo)/2
		if rune(caseOrbit[m].From) < r {
			lo = m + 1
		} else {
			hi = m
		}
	}
	if lo < len(caseOrbit) && rune(caseOrbit[lo].From) == r {
		return rune(caseOrbit[lo].To)
	}

	// No folding specified.  This is a one- or two-element
	// equivalence class containing rune and ToLower(rune)
	// and ToUpper(rune) if they are different from rune.
	if l := ToLower(r); l != r {
		return l
	}
	return ToUpper(r)
}
<|endoftext|>


<commit_before>// Copyright 2009 The Go 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 utf8_test

import (
	"bytes";
	"strings";
	"testing";
	. "utf8";
)

type Utf8Map struct {
	rune	int;
	str	string;
}

var utf8map = []Utf8Map{
	Utf8Map{0x0000, "\x00"},
	Utf8Map{0x0001, "\x01"},
	Utf8Map{0x007e, "\x7e"},
	Utf8Map{0x007f, "\x7f"},
	Utf8Map{0x0080, "\xc2\x80"},
	Utf8Map{0x0081, "\xc2\x81"},
	Utf8Map{0x00bf, "\xc2\xbf"},
	Utf8Map{0x00c0, "\xc3\x80"},
	Utf8Map{0x00c1, "\xc3\x81"},
	Utf8Map{0x00c8, "\xc3\x88"},
	Utf8Map{0x00d0, "\xc3\x90"},
	Utf8Map{0x00e0, "\xc3\xa0"},
	Utf8Map{0x00f0, "\xc3\xb0"},
	Utf8Map{0x00f8, "\xc3\xb8"},
	Utf8Map{0x00ff, "\xc3\xbf"},
	Utf8Map{0x0100, "\xc4\x80"},
	Utf8Map{0x07ff, "\xdf\xbf"},
	Utf8Map{0x0800, "\xe0\xa0\x80"},
	Utf8Map{0x0801, "\xe0\xa0\x81"},
	Utf8Map{0xfffe, "\xef\xbf\xbe"},
	Utf8Map{0xffff, "\xef\xbf\xbf"},
	Utf8Map{0x10000, "\xf0\x90\x80\x80"},
	Utf8Map{0x10001, "\xf0\x90\x80\x81"},
	Utf8Map{0x10fffe, "\xf4\x8f\xbf\xbe"},
	Utf8Map{0x10ffff, "\xf4\x8f\xbf\xbf"},
}

// strings.Bytes with one extra byte at end
func makeBytes(s string) []byte {
	s += "\x00";
	b := strings.Bytes(s);
	return b[0 : len(s)-1];
}

func TestFullRune(t *testing.T) {
	for i := 0; i < len(utf8map); i++ {
		m := utf8map[i];
		b := makeBytes(m.str);
		if !FullRune(b) {
			t.Errorf("FullRune(%q) (rune %04x) = false, want true", b, m.rune)
		}
		s := m.str;
		if !FullRuneInString(s) {
			t.Errorf("FullRuneInString(%q) (rune %04x) = false, want true", s, m.rune)
		}
		b1 := b[0 : len(b)-1];
		if FullRune(b1) {
			t.Errorf("FullRune(%q) = true, want false", b1)
		}
		s1 := string(b1);
		if FullRuneInString(s1) {
			t.Errorf("FullRune(%q) = true, want false", s1)
		}
	}
}

func TestEncodeRune(t *testing.T) {
	for i := 0; i < len(utf8map); i++ {
		m := utf8map[i];
		b := makeBytes(m.str);
		var buf [10]byte;
		n := EncodeRune(m.rune, &buf);
		b1 := buf[0:n];
		if !bytes.Equal(b, b1) {
			t.Errorf("EncodeRune(0x%04x) = %q want %q", m.rune, b1, b)
		}
	}
}

func TestDecodeRune(t *testing.T) {
	for i := 0; i < len(utf8map); i++ {
		m := utf8map[i];
		b := makeBytes(m.str);
		rune, size := DecodeRune(b);
		if rune != m.rune || size != len(b) {
			t.Errorf("DecodeRune(%q) = 0x%04x, %d want 0x%04x, %d", b, rune, size, m.rune, len(b))
		}
		s := m.str;
		rune, size = DecodeRuneInString(s);
		if rune != m.rune || size != len(b) {
			t.Errorf("DecodeRune(%q) = 0x%04x, %d want 0x%04x, %d", s, rune, size, m.rune, len(b))
		}

		// there's an extra byte that bytes left behind - make sure trailing byte works
		rune, size = DecodeRune(b[0:cap(b)]);
		if rune != m.rune || size != len(b) {
			t.Errorf("DecodeRune(%q) = 0x%04x, %d want 0x%04x, %d", b, rune, size, m.rune, len(b))
		}
		s = m.str + "\x00";
		rune, size = DecodeRuneInString(s);
		if rune != m.rune || size != len(b) {
			t.Errorf("DecodeRuneInString(%q) = 0x%04x, %d want 0x%04x, %d", s, rune, size, m.rune, len(b))
		}

		// make sure missing bytes fail
		wantsize := 1;
		if wantsize >= len(b) {
			wantsize = 0
		}
		rune, size = DecodeRune(b[0 : len(b)-1]);
		if rune != RuneError || size != wantsize {
			t.Errorf("DecodeRune(%q) = 0x%04x, %d want 0x%04x, %d", b[0:len(b)-1], rune, size, RuneError, wantsize)
		}
		s = m.str[0 : len(m.str)-1];
		rune, size = DecodeRuneInString(s);
		if rune != RuneError || size != wantsize {
			t.Errorf("DecodeRuneInString(%q) = 0x%04x, %d want 0x%04x, %d", s, rune, size, RuneError, wantsize)
		}

		// make sure bad sequences fail
		if len(b) == 1 {
			b[0] = 0x80
		} else {
			b[len(b)-1] = 0x7F
		}
		rune, size = DecodeRune(b);
		if rune != RuneError || size != 1 {
			t.Errorf("DecodeRune(%q) = 0x%04x, %d want 0x%04x, %d", b, rune, size, RuneError, 1)
		}
		s = string(b);
		rune, size = DecodeRune(b);
		if rune != RuneError || size != 1 {
			t.Errorf("DecodeRuneInString(%q) = 0x%04x, %d want 0x%04x, %d", s, rune, size, RuneError, 1)
		}
	}
}

type RuneCountTest struct {
	in	string;
	out	int;
}

var runecounttests = []RuneCountTest{
	RuneCountTest{"abcd", 4},
	RuneCountTest{"☺☻☹", 3},
	RuneCountTest{"1,2,3,4", 7},
	RuneCountTest{"\xe2\x00", 2},
}

func TestRuneCount(t *testing.T) {
	for i := 0; i < len(runecounttests); i++ {
		tt := runecounttests[i];
		if out := RuneCountInString(tt.in); out != tt.out {
			t.Errorf("RuneCountInString(%q) = %d, want %d", tt.in, out, tt.out)
		}
		if out := RuneCount(makeBytes(tt.in)); out != tt.out {
			t.Errorf("RuneCount(%q) = %d, want %d", tt.in, out, tt.out)
		}
	}
}
<commit_msg>a few utf8 benchmarks. on my mac:<commit_after>// Copyright 2009 The Go 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 utf8_test

import (
	"bytes";
	"strings";
	"testing";
	. "utf8";
)

type Utf8Map struct {
	rune	int;
	str	string;
}

var utf8map = []Utf8Map{
	Utf8Map{0x0000, "\x00"},
	Utf8Map{0x0001, "\x01"},
	Utf8Map{0x007e, "\x7e"},
	Utf8Map{0x007f, "\x7f"},
	Utf8Map{0x0080, "\xc2\x80"},
	Utf8Map{0x0081, "\xc2\x81"},
	Utf8Map{0x00bf, "\xc2\xbf"},
	Utf8Map{0x00c0, "\xc3\x80"},
	Utf8Map{0x00c1, "\xc3\x81"},
	Utf8Map{0x00c8, "\xc3\x88"},
	Utf8Map{0x00d0, "\xc3\x90"},
	Utf8Map{0x00e0, "\xc3\xa0"},
	Utf8Map{0x00f0, "\xc3\xb0"},
	Utf8Map{0x00f8, "\xc3\xb8"},
	Utf8Map{0x00ff, "\xc3\xbf"},
	Utf8Map{0x0100, "\xc4\x80"},
	Utf8Map{0x07ff, "\xdf\xbf"},
	Utf8Map{0x0800, "\xe0\xa0\x80"},
	Utf8Map{0x0801, "\xe0\xa0\x81"},
	Utf8Map{0xfffe, "\xef\xbf\xbe"},
	Utf8Map{0xffff, "\xef\xbf\xbf"},
	Utf8Map{0x10000, "\xf0\x90\x80\x80"},
	Utf8Map{0x10001, "\xf0\x90\x80\x81"},
	Utf8Map{0x10fffe, "\xf4\x8f\xbf\xbe"},
	Utf8Map{0x10ffff, "\xf4\x8f\xbf\xbf"},
}

// strings.Bytes with one extra byte at end
func makeBytes(s string) []byte {
	s += "\x00";
	b := strings.Bytes(s);
	return b[0 : len(s)-1];
}

func TestFullRune(t *testing.T) {
	for i := 0; i < len(utf8map); i++ {
		m := utf8map[i];
		b := makeBytes(m.str);
		if !FullRune(b) {
			t.Errorf("FullRune(%q) (rune %04x) = false, want true", b, m.rune)
		}
		s := m.str;
		if !FullRuneInString(s) {
			t.Errorf("FullRuneInString(%q) (rune %04x) = false, want true", s, m.rune)
		}
		b1 := b[0 : len(b)-1];
		if FullRune(b1) {
			t.Errorf("FullRune(%q) = true, want false", b1)
		}
		s1 := string(b1);
		if FullRuneInString(s1) {
			t.Errorf("FullRune(%q) = true, want false", s1)
		}
	}
}

func TestEncodeRune(t *testing.T) {
	for i := 0; i < len(utf8map); i++ {
		m := utf8map[i];
		b := makeBytes(m.str);
		var buf [10]byte;
		n := EncodeRune(m.rune, &buf);
		b1 := buf[0:n];
		if !bytes.Equal(b, b1) {
			t.Errorf("EncodeRune(0x%04x) = %q want %q", m.rune, b1, b)
		}
	}
}

func TestDecodeRune(t *testing.T) {
	for i := 0; i < len(utf8map); i++ {
		m := utf8map[i];
		b := makeBytes(m.str);
		rune, size := DecodeRune(b);
		if rune != m.rune || size != len(b) {
			t.Errorf("DecodeRune(%q) = 0x%04x, %d want 0x%04x, %d", b, rune, size, m.rune, len(b))
		}
		s := m.str;
		rune, size = DecodeRuneInString(s);
		if rune != m.rune || size != len(b) {
			t.Errorf("DecodeRune(%q) = 0x%04x, %d want 0x%04x, %d", s, rune, size, m.rune, len(b))
		}

		// there's an extra byte that bytes left behind - make sure trailing byte works
		rune, size = DecodeRune(b[0:cap(b)]);
		if rune != m.rune || size != len(b) {
			t.Errorf("DecodeRune(%q) = 0x%04x, %d want 0x%04x, %d", b, rune, size, m.rune, len(b))
		}
		s = m.str + "\x00";
		rune, size = DecodeRuneInString(s);
		if rune != m.rune || size != len(b) {
			t.Errorf("DecodeRuneInString(%q) = 0x%04x, %d want 0x%04x, %d", s, rune, size, m.rune, len(b))
		}

		// make sure missing bytes fail
		wantsize := 1;
		if wantsize >= len(b) {
			wantsize = 0
		}
		rune, size = DecodeRune(b[0 : len(b)-1]);
		if rune != RuneError || size != wantsize {
			t.Errorf("DecodeRune(%q) = 0x%04x, %d want 0x%04x, %d", b[0:len(b)-1], rune, size, RuneError, wantsize)
		}
		s = m.str[0 : len(m.str)-1];
		rune, size = DecodeRuneInString(s);
		if rune != RuneError || size != wantsize {
			t.Errorf("DecodeRuneInString(%q) = 0x%04x, %d want 0x%04x, %d", s, rune, size, RuneError, wantsize)
		}

		// make sure bad sequences fail
		if len(b) == 1 {
			b[0] = 0x80
		} else {
			b[len(b)-1] = 0x7F
		}
		rune, size = DecodeRune(b);
		if rune != RuneError || size != 1 {
			t.Errorf("DecodeRune(%q) = 0x%04x, %d want 0x%04x, %d", b, rune, size, RuneError, 1)
		}
		s = string(b);
		rune, size = DecodeRune(b);
		if rune != RuneError || size != 1 {
			t.Errorf("DecodeRuneInString(%q) = 0x%04x, %d want 0x%04x, %d", s, rune, size, RuneError, 1)
		}
	}
}

type RuneCountTest struct {
	in	string;
	out	int;
}

var runecounttests = []RuneCountTest{
	RuneCountTest{"abcd", 4},
	RuneCountTest{"☺☻☹", 3},
	RuneCountTest{"1,2,3,4", 7},
	RuneCountTest{"\xe2\x00", 2},
}

func TestRuneCount(t *testing.T) {
	for i := 0; i < len(runecounttests); i++ {
		tt := runecounttests[i];
		if out := RuneCountInString(tt.in); out != tt.out {
			t.Errorf("RuneCountInString(%q) = %d, want %d", tt.in, out, tt.out)
		}
		if out := RuneCount(makeBytes(tt.in)); out != tt.out {
			t.Errorf("RuneCount(%q) = %d, want %d", tt.in, out, tt.out)
		}
	}
}

func BenchmarkRuneCountTenASCIIChars(b *testing.B) {
	for i := 0; i < b.N; i++ {
		RuneCountInString("0123456789")
	}
}

func BenchmarkRuneCountTenJapaneseChars(b *testing.B) {
	for i := 0; i < b.N; i++ {
		RuneCountInString("日本語日本語日本語日")
	}
}

func BenchmarkEncodeASCIIRune(b *testing.B) {
	buf := make([]byte, UTFMax);
	for i := 0; i < b.N; i++ {
		EncodeRune('a', buf)
	}
}

func BenchmarkEncodeJapaneseRune(b *testing.B) {
	buf := make([]byte, UTFMax);
	for i := 0; i < b.N; i++ {
		EncodeRune('本', buf)
	}
}

func BenchmarkDecodeASCIIRune(b *testing.B) {
	a := []byte{'a'};
	for i := 0; i < b.N; i++ {
		DecodeRune(a)
	}
}

func BenchmarkDecodeJapaneseRune(b *testing.B) {
	nihon := strings.Bytes("本");
	for i := 0; i < b.N; i++ {
		DecodeRune(nihon)
	}
}
<|endoftext|>


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

// The git-cleanup command deletes branches which have already
// been merged to the Gerrit server.
package main

import (
	"bufio"
	"bytes"
	"log"
	"os/exec"
	"regexp"
	"strings"
)

func main() {
	curBranch := ""
	out, err := exec.Command("git", "branch").Output()
	if err != nil {
		log.Fatalf("Error running git branch: %v", err)
	}
	var branches []string
	for _, line := range strings.Split(string(out), "\n") {
		line = strings.TrimSpace(line)
		cur := false
		if strings.HasPrefix(line, "* ") {
			line = line[2:]
			cur = true
		}
		if line == "" {
			continue
		}
		branches = append(branches, line)
		if cur {
			curBranch = line
		}
	}
	if curBranch != "master" {
		log.Fatalf("On branch %s; must be on master", curBranch)
	}

	for _, br := range branches {
		if br == "master" {
			continue
		}
		if isSubmitted(branchChangeID(br)) {
			// Display a ref for the branch we're about to delete,
			// so that if we screw up (never!), the user can get it back easily.
			short, err := exec.Command("git", "rev-parse", "--short", "refs/heads/"+br).Output()
			if err != nil {
				log.Fatalf("Error running git rev-parse: %v", err)
			}
			short = bytes.TrimSpace(short)
			log.Printf("Removing branch %s (%s) ...", br, short)
			if out, err := exec.Command("git", "branch", "-D", br).CombinedOutput(); err != nil {
				log.Printf("Error removing branch %s: %v, %s", br, err, out)
			}

			// Remove corresponding tag, if there is one.
			tag := br + ".mailed"
			short, err = exec.Command("git", "rev-parse", "--short", "refs/tags/"+tag).Output()
			if err != nil {
				continue
			}
			short = bytes.TrimSpace(short)
			log.Printf("Removing tag %s (%s) ...", tag, short)
			if out, err := exec.Command("git", "tag", "-d", tag).CombinedOutput(); err != nil {
				log.Printf("Error removing tag %s: %v, %s", tag, err, out)
			}
		}
	}
}

var changeRx = regexp.MustCompile(`(?m)^\s*Change-Id: (I[0-9a-f]+)`)

// returns change-id or the empty string
func branchChangeID(br string) string {
	out, err := exec.Command("git", "show", br, "--").CombinedOutput()
	if err != nil {
		log.Printf("Error running git show %v: %v: %s", br, err, out)
	}
	if m := changeRx.FindSubmatch(out); m != nil {
		return string(m[1])
	}
	return ""
}

func isSubmitted(changeID string) bool {
	if changeID == "" {
		return false
	}
	for _, v := range changeIDLog() {
		if v == changeID {
			return true
		}
	}
	return false
}

var changeIDLogCache []string

func changeIDLog() (ret []string) {
	if v := changeIDLogCache; v != nil {
		return v
	}
	defer func() { changeIDLogCache = ret }()
	cmd := exec.Command("git", "log", "-F", "--grep", "Change-Id:")
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		log.Fatalf("pipe error: %v", err)
	}
	if err := cmd.Start(); err != nil {
		log.Fatalf("error running git log: %v", err)
	}
	bs := bufio.NewScanner(stdout)
	wantLine := []byte("Change-Id: ")
	for bs.Scan() {
		if !bytes.Contains(bs.Bytes(), wantLine) {
			continue
		}
		changeID := strings.TrimPrefix(strings.TrimSpace(bs.Text()), "Change-Id: ")
		ret = append(ret, changeID)
	}
	if err := bs.Err(); err != nil {
		log.Fatalf("error running git log: %v", err)
	}
	if err := cmd.Wait(); err != nil {
		log.Fatalf("error running git log: %v", err)
	}
	return
}
<commit_msg>Support dev branches<commit_after>// Copyright 2014 Google. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// The git-cleanup command deletes branches which have already
// been merged to the Gerrit server.
package main

import (
	"bufio"
	"bytes"
	"log"
	"os/exec"
	"regexp"
	"strings"
)

func main() {
	curBranch := ""
	out, err := exec.Command("git", "branch").Output()
	if err != nil {
		log.Fatalf("Error running git branch: %v", err)
	}
	var branches []string
	for _, line := range strings.Split(string(out), "\n") {
		line = strings.TrimSpace(line)
		cur := false
		if strings.HasPrefix(line, "* ") {
			line = line[2:]
			cur = true
		}
		if line == "" {
			continue
		}
		branches = append(branches, line)
		if cur {
			curBranch = line
		}
	}
	if !isMainBranch(curBranch) {
		log.Fatalf("On branch %s; must be on master or a dev branch", curBranch)
	}

	for _, br := range branches {
		if isMainBranch(br) {
			continue
		}
		if isSubmitted(branchChangeID(br)) {
			// Display a ref for the branch we're about to delete,
			// so that if we screw up (never!), the user can get it back easily.
			short, err := exec.Command("git", "rev-parse", "--short", "refs/heads/"+br).Output()
			if err != nil {
				log.Fatalf("Error running git rev-parse: %v", err)
			}
			short = bytes.TrimSpace(short)
			log.Printf("Removing branch %s (%s) ...", br, short)
			if out, err := exec.Command("git", "branch", "-D", br).CombinedOutput(); err != nil {
				log.Printf("Error removing branch %s: %v, %s", br, err, out)
			}

			// Remove corresponding tag, if there is one.
			tag := br + ".mailed"
			short, err = exec.Command("git", "rev-parse", "--short", "refs/tags/"+tag).Output()
			if err != nil {
				continue
			}
			short = bytes.TrimSpace(short)
			log.Printf("Removing tag %s (%s) ...", tag, short)
			if out, err := exec.Command("git", "tag", "-d", tag).CombinedOutput(); err != nil {
				log.Printf("Error removing tag %s: %v, %s", tag, err, out)
			}
		}
	}
}

var changeRx = regexp.MustCompile(`(?m)^\s*Change-Id: (I[0-9a-f]+)`)

// returns change-id or the empty string
func branchChangeID(br string) string {
	out, err := exec.Command("git", "show", br, "--").CombinedOutput()
	if err != nil {
		log.Printf("Error running git show %v: %v: %s", br, err, out)
	}
	if m := changeRx.FindSubmatch(out); m != nil {
		return string(m[1])
	}
	return ""
}

// isMainBranch reports whether br is a shared development branch.
func isMainBranch(br string) bool {
	return br == "master" || strings.HasPrefix(br, "dev.")
}

func isSubmitted(changeID string) bool {
	if changeID == "" {
		return false
	}
	for _, v := range changeIDLog() {
		if v == changeID {
			return true
		}
	}
	return false
}

var changeIDLogCache []string

func changeIDLog() (ret []string) {
	if v := changeIDLogCache; v != nil {
		return v
	}
	defer func() { changeIDLogCache = ret }()
	cmd := exec.Command("git", "log", "-F", "--grep", "Change-Id:")
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		log.Fatalf("pipe error: %v", err)
	}
	if err := cmd.Start(); err != nil {
		log.Fatalf("error running git log: %v", err)
	}
	bs := bufio.NewScanner(stdout)
	wantLine := []byte("Change-Id: ")
	for bs.Scan() {
		if !bytes.Contains(bs.Bytes(), wantLine) {
			continue
		}
		changeID := strings.TrimPrefix(strings.TrimSpace(bs.Text()), "Change-Id: ")
		ret = append(ret, changeID)
	}
	if err := bs.Err(); err != nil {
		log.Fatalf("error running git log: %v", err)
	}
	if err := cmd.Wait(); err != nil {
		log.Fatalf("error running git log: %v", err)
	}
	return
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"crypto/md5"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"

	"github.com/flynn/go-crypto-ssh"
	"github.com/flynn/go-shlex"
)

const PrereceiveHook = `#!/bin/bash
set -eo pipefail; while read oldrev newrev refname; do
[[ $refname = "refs/heads/master" ]] && git archive $newrev | {{RECEIVER}} "$RECEIVE_USER" "$RECEIVE_REPO" "$RECEIVE_KEYNAME" "$RECEIVE_FINGERPRINT" | sed -$([[ $(uname) == "Darwin" ]] && echo l || echo u) "s/^/"$'\e[1G'"/"
done
`

var port *string = flag.String("p", "22", "port to listen on")
var repoPath *string = flag.String("r", "/tmp/repos", "path to repo cache")
var keyPath *string = flag.String("k", "/tmp/keys", "path to named keys")
var noAuth *bool = flag.Bool("n", false, "no client authentication")

var receiver string
var privateKey string
var keyNames = make(map[string]string)

func init() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage:  %v [options] <privatekey> <receiver>\n\n", os.Args[0])
		flag.PrintDefaults()
	}
}

func main() {
	flag.Parse()
	if len(os.Args) < 2 {
		flag.Usage()
		return
	}
	privateKey = flag.Arg(0)
	receiver = flag.Arg(1)

	var config *ssh.ServerConfig
	if *noAuth {
		config = &ssh.ServerConfig{
			NoClientAuth: true,
		}
	} else {
		config = &ssh.ServerConfig{
			PublicKeyCallback: func(conn *ssh.ServerConn, user, algo string, pubkey []byte) bool {
				clientkey, _, ok := ssh.ParsePublicKey(pubkey)
				if !ok {
					return false
				}
				os.MkdirAll(*keyPath, 0755)
				files, err := ioutil.ReadDir(*keyPath)
				if err != nil {
					panic(err)
				}
				for _, file := range files {
					if !file.IsDir() {
						data, err := ioutil.ReadFile(*keyPath + "/" + file.Name())
						if err != nil {
							panic(err)
						}
						filekey, _, _, _, ok := ssh.ParseAuthorizedKey(data)
						if !ok {
							continue
						}
						if bytes.Equal(clientkey.Marshal(), filekey.Marshal()) {
							keyNames[publicKeyFingerprint(clientkey)] = file.Name()
							return true
						}
					}
				}
				return false
			},
		}
	}

	pemBytes, err := ioutil.ReadFile(privateKey)
	if err != nil {
		log.Fatal("Failed to load private key:", err)
	}
	if err = config.SetRSAPrivateKey(pemBytes); err != nil {
		log.Fatal("Failed to parse private key:", err)
	}

	listener, err := ssh.Listen("tcp", "0.0.0.0:"+*port, config)
	if err != nil {
		log.Fatal("failed to listen for connection")
	}
	for {
		// SSH connections just house multiplexed connections
		conn, err := listener.Accept()
		if err != nil {
			log.Println("failed to accept incoming connection")
			continue
		}
		if err := conn.Handshake(); err != nil {
			log.Println("failed to handshake")
			continue
		}
		go handleConnection(conn)
	}
}

func handleConnection(conn *ssh.ServerConn) {
	defer conn.Close()
	for {
		// Accept reads from the connection, demultiplexes packets
		// to their corresponding channels and returns when a new
		// channel request is seen. Some goroutine must always be
		// calling Accept; otherwise no messages will be forwarded
		// to the channels.
		ch, err := conn.Accept()
		if err == io.EOF {
			return
		}
		if err != nil {
			log.Println("hanndleConnection Accept:", err)
			break
		}
		if ch.ChannelType() != "session" {
			ch.Reject(ssh.UnknownChannelType, "unknown channel type")
			break
		}
		go handleChannel(conn, ch)
	}
}

func handleChannel(conn *ssh.ServerConn, ch ssh.Channel) {
	err := ch.Accept()
	if err != nil {
		panic(err)
	}
	defer ch.Close()
	for {
		req, err := ch.ReadRequest()
		if err == io.EOF {
			return
		}
		if err != nil {
			log.Println("handleChannel read request err:", err)
			continue
		}
		switch req.Request {
		case "exec":
			if req.WantReply {
				ch.AckRequest(true)
			}
			cmdline := string(req.Payload[4:])
			cmdargs, err := shlex.Split(cmdline)
			if err != nil {
				panic(err)
			}
			ensureCacheRepo(cmdargs[1])
			var keyname, fingerprint string
			if *noAuth {
				fingerprint = ""
				keyname = ""
			} else {
				fingerprint = publicKeyFingerprint(conn.PublicKey)
				keyname = keyNames[fingerprint]
			}
			cmd := exec.Command(cmdargs[0], cmdargs[1:]...)
			cmd.Dir = *repoPath
			cmd.Env = []string{
				"RECEIVE_USER=" + conn.User,
				"RECEIVE_REPO=" + cmdargs[1],
				"RECEIVE_KEYNAME=" + keyname,
				"RECEIVE_FINGERPRINT=" + fingerprint,
			}
			errCh := attachCmd(cmd, ch, ch.Stderr(), ch)
			err = cmd.Start()
			if err != nil {
				panic(err)
			}
			exitCh := exitStatusCh(cmd)
			if err = <-errCh; err != nil {
				panic(err)
			}
			ch.Exit(<-exitCh)
		case "env":
			if req.WantReply {
				ch.AckRequest(true)
			}
		}
	}
}

func attachCmd(cmd *exec.Cmd, stdout, stderr io.Writer, stdin io.Reader) chan error {
	errCh := make(chan error)

	stdinIn, err := cmd.StdinPipe()
	if err != nil {
		panic(err)
	}
	stdoutOut, err := cmd.StdoutPipe()
	if err != nil {
		panic(err)
	}
	stderrOut, err := cmd.StderrPipe()
	if err != nil {
		panic(err)
	}

	go func() {
		_, e := io.Copy(stdinIn, stdin)
		errCh <- e
	}()
	go func() {
		_, e := io.Copy(stdout, stdoutOut)
		errCh <- e
	}()
	go func() {
		_, e := io.Copy(stderr, stderrOut)
		errCh <- e
	}()

	return errCh
}

func exitStatusCh(cmd *exec.Cmd) chan uint {
	exitCh := make(chan uint)
	go func() {
		err := cmd.Wait()
		if err != nil {
			if exiterr, ok := err.(*exec.ExitError); ok {
				// There is no plattform independent way to retrieve
				// the exit code, but the following will work on Unix
				if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
					exitCh <- uint(status.ExitStatus())
				}
			} else {
				panic(err)
			}
			return
		}
		exitCh <- uint(0)
	}()
	return exitCh
}

func ensureCacheRepo(path string) {
	cachePath := *repoPath + "/" + path
	if _, err := os.Stat(cachePath); os.IsNotExist(err) {
		os.MkdirAll(cachePath, 0755)
		cmd := exec.Command("git", "init", "--bare")
		cmd.Dir = cachePath
		err = cmd.Run()
		if err != nil {
			panic(err)
		}
	}
	receiver, err := filepath.Abs(receiver)
	if err != nil {
		panic(err)
	}
	ioutil.WriteFile(
		cachePath+"/hooks/pre-receive",
		[]byte(strings.Replace(PrereceiveHook, "{{RECEIVER}}", receiver, 1)),
		0755)
}

func publicKeyFingerprint(key ssh.PublicKey) string {
	var values []string
	h := md5.New()
	h.Write(ssh.MarshalPublicKey(key))
	for _, value := range h.Sum(nil) {
		values = append(values, fmt.Sprintf("%x", value))
	}
	return strings.Join(values, ":")
}
<commit_msg>gitreceived: disallow root repos. also lets you use ssh:// syntax in remotes<commit_after>package main

import (
	"bytes"
	"crypto/md5"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"

	"github.com/flynn/go-crypto-ssh"
	"github.com/flynn/go-shlex"
)

const PrereceiveHook = `#!/bin/bash
set -eo pipefail; while read oldrev newrev refname; do
[[ $refname = "refs/heads/master" ]] && git archive $newrev | {{RECEIVER}} "$RECEIVE_USER" "$RECEIVE_REPO" "$RECEIVE_KEYNAME" "$RECEIVE_FINGERPRINT" | sed -$([[ $(uname) == "Darwin" ]] && echo l || echo u) "s/^/"$'\e[1G'"/"
done
`

var port *string = flag.String("p", "22", "port to listen on")
var repoPath *string = flag.String("r", "/tmp/repos", "path to repo cache")
var keyPath *string = flag.String("k", "/tmp/keys", "path to named keys")
var noAuth *bool = flag.Bool("n", false, "no client authentication")

var receiver string
var privateKey string
var keyNames = make(map[string]string)

func init() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage:  %v [options] <privatekey> <receiver>\n\n", os.Args[0])
		flag.PrintDefaults()
	}
}

func main() {
	flag.Parse()
	if len(os.Args) < 2 {
		flag.Usage()
		return
	}
	privateKey = flag.Arg(0)
	receiver = flag.Arg(1)

	var config *ssh.ServerConfig
	if *noAuth {
		config = &ssh.ServerConfig{
			NoClientAuth: true,
		}
	} else {
		config = &ssh.ServerConfig{
			PublicKeyCallback: func(conn *ssh.ServerConn, user, algo string, pubkey []byte) bool {
				clientkey, _, ok := ssh.ParsePublicKey(pubkey)
				if !ok {
					return false
				}
				os.MkdirAll(*keyPath, 0755)
				files, err := ioutil.ReadDir(*keyPath)
				if err != nil {
					panic(err)
				}
				for _, file := range files {
					if !file.IsDir() {
						data, err := ioutil.ReadFile(*keyPath + "/" + file.Name())
						if err != nil {
							panic(err)
						}
						filekey, _, _, _, ok := ssh.ParseAuthorizedKey(data)
						if !ok {
							continue
						}
						if bytes.Equal(clientkey.Marshal(), filekey.Marshal()) {
							keyNames[publicKeyFingerprint(clientkey)] = file.Name()
							return true
						}
					}
				}
				return false
			},
		}
	}

	pemBytes, err := ioutil.ReadFile(privateKey)
	if err != nil {
		log.Fatal("Failed to load private key:", err)
	}
	if err = config.SetRSAPrivateKey(pemBytes); err != nil {
		log.Fatal("Failed to parse private key:", err)
	}

	listener, err := ssh.Listen("tcp", "0.0.0.0:"+*port, config)
	if err != nil {
		log.Fatal("failed to listen for connection")
	}
	for {
		// SSH connections just house multiplexed connections
		conn, err := listener.Accept()
		if err != nil {
			log.Println("failed to accept incoming connection")
			continue
		}
		if err := conn.Handshake(); err != nil {
			log.Println("failed to handshake")
			continue
		}
		go handleConnection(conn)
	}
}

func handleConnection(conn *ssh.ServerConn) {
	defer conn.Close()
	for {
		// Accept reads from the connection, demultiplexes packets
		// to their corresponding channels and returns when a new
		// channel request is seen. Some goroutine must always be
		// calling Accept; otherwise no messages will be forwarded
		// to the channels.
		ch, err := conn.Accept()
		if err == io.EOF {
			return
		}
		if err != nil {
			log.Println("hanndleConnection Accept:", err)
			break
		}
		if ch.ChannelType() != "session" {
			ch.Reject(ssh.UnknownChannelType, "unknown channel type")
			break
		}
		go handleChannel(conn, ch)
	}
}

func handleChannel(conn *ssh.ServerConn, ch ssh.Channel) {
	err := ch.Accept()
	if err != nil {
		panic(err)
	}
	defer ch.Close()
	for {
		req, err := ch.ReadRequest()
		if err == io.EOF {
			return
		}
		if err != nil {
			log.Println("handleChannel read request err:", err)
			continue
		}
		switch req.Request {
		case "exec":
			if req.WantReply {
				ch.AckRequest(true)
			}
			cmdline := string(req.Payload[4:])
			cmdargs, err := shlex.Split(cmdline)
			if err != nil {
				panic(err)
			}
			if strings.HasPrefix(cmdargs[1], "/") {
				cmdargs[1] = cmdargs[1][1:]
			}
			ensureCacheRepo(cmdargs[1])
			var keyname, fingerprint string
			if *noAuth {
				fingerprint = ""
				keyname = ""
			} else {
				fingerprint = publicKeyFingerprint(conn.PublicKey)
				keyname = keyNames[fingerprint]
			}
			cmd := exec.Command(cmdargs[0], cmdargs[1:]...)
			cmd.Dir = *repoPath
			cmd.Env = []string{
				"RECEIVE_USER=" + conn.User,
				"RECEIVE_REPO=" + cmdargs[1],
				"RECEIVE_KEYNAME=" + keyname,
				"RECEIVE_FINGERPRINT=" + fingerprint,
			}
			errCh := attachCmd(cmd, ch, ch.Stderr(), ch)
			err = cmd.Start()
			if err != nil {
				panic(err)
			}
			exitCh := exitStatusCh(cmd)
			if err = <-errCh; err != nil {
				panic(err)
			}
			ch.Exit(<-exitCh)
		case "env":
			if req.WantReply {
				ch.AckRequest(true)
			}
		}
	}
}

func attachCmd(cmd *exec.Cmd, stdout, stderr io.Writer, stdin io.Reader) chan error {
	errCh := make(chan error)

	stdinIn, err := cmd.StdinPipe()
	if err != nil {
		panic(err)
	}
	stdoutOut, err := cmd.StdoutPipe()
	if err != nil {
		panic(err)
	}
	stderrOut, err := cmd.StderrPipe()
	if err != nil {
		panic(err)
	}

	go func() {
		_, e := io.Copy(stdinIn, stdin)
		errCh <- e
	}()
	go func() {
		_, e := io.Copy(stdout, stdoutOut)
		errCh <- e
	}()
	go func() {
		_, e := io.Copy(stderr, stderrOut)
		errCh <- e
	}()

	return errCh
}

func exitStatusCh(cmd *exec.Cmd) chan uint {
	exitCh := make(chan uint)
	go func() {
		err := cmd.Wait()
		if err != nil {
			if exiterr, ok := err.(*exec.ExitError); ok {
				// There is no plattform independent way to retrieve
				// the exit code, but the following will work on Unix
				if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
					exitCh <- uint(status.ExitStatus())
				}
			} else {
				panic(err)
			}
			return
		}
		exitCh <- uint(0)
	}()
	return exitCh
}

func ensureCacheRepo(path string) {
	cachePath := *repoPath + "/" + path
	if _, err := os.Stat(cachePath); os.IsNotExist(err) {
		os.MkdirAll(cachePath, 0755)
		cmd := exec.Command("git", "init", "--bare")
		cmd.Dir = cachePath
		err = cmd.Run()
		if err != nil {
			panic(err)
		}
	}
	receiver, err := filepath.Abs(receiver)
	if err != nil {
		panic(err)
	}
	ioutil.WriteFile(
		cachePath+"/hooks/pre-receive",
		[]byte(strings.Replace(PrereceiveHook, "{{RECEIVER}}", receiver, 1)),
		0755)
}

func publicKeyFingerprint(key ssh.PublicKey) string {
	var values []string
	h := md5.New()
	h.Write(ssh.MarshalPublicKey(key))
	for _, value := range h.Sum(nil) {
		values = append(values, fmt.Sprintf("%x", value))
	}
	return strings.Join(values, ":")
}
<|endoftext|>


<commit_before>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package engine

import (
	"fmt"
	"testing"

	"github.com/keybase/client/go/libkb"
	keybase1 "github.com/keybase/client/go/protocol"
)

func TestFavoriteAdd(t *testing.T) {
	tc := SetupEngineTest(t, "template")
	defer tc.Cleanup()
	CreateAndSignupFakeUser(tc, "fav")

	idUI := &FakeIdentifyUI{}
	addfav("t_alice,t_bob", true, true, idUI, tc)
	if len(listfav(tc)) != 3 {
		t.Errorf("favorites len: %d, expected 3", len(listfav(tc)))
	}

	// Add the same share again. The number shouldn't change.
	addfav("t_alice,t_bob", true, true, idUI, tc)
	if len(listfav(tc)) != 3 {
		t.Errorf("favorites len: %d, expected 3", len(listfav(tc)))
	}

	// Add a public share of the same name, make sure both are represented.
	addfav("t_alice,t_bob", false, true, idUI, tc)
	if len(listfav(tc)) != 4 {
		t.Errorf("favorites len: %d, expected 4", len(listfav(tc)))
	}
}

// Test adding a favorite with a social assertion.
// Sharing before signup, social assertion user doesn't
// exist yet.
func TestFavoriteAddSocial(t *testing.T) {
	tc := SetupEngineTest(t, "template")
	defer tc.Cleanup()
	u := CreateAndSignupFakeUser(tc, "fav")

	idUI := &FakeIdentifyUI{}
	addfav(fmt.Sprintf("%s,bob@twitter", u.Username), true, true, idUI, tc)
	if len(listfav(tc)) != 3 {
		t.Errorf("favorites len: %d, expected 3", len(listfav(tc)))
	}

	if idUI.DisplayTLFCount != 1 {
		t.Errorf("DisplayTLFCount: %d, expected 1", idUI.DisplayTLFCount)
	}
	// There's no way to give invites to a user via API, so the
	// only case we can test automatically is the user being
	// out of invites.
	if !idUI.DisplayTLFArg.Throttled {
		t.Errorf("DisplayTLFArg.Throttled not set, expected it to be since user has no invites.")
	}
	if !idUI.DisplayTLFArg.IsPrivate {
		t.Errorf("DisplayTLFArg.IsPrivate not set on a private folder")
	}

	idUI = &FakeIdentifyUI{}
	// Test adding a favorite when not the creator.  Should not call ui for
	// displaying tlf + invite.
	// created flag == false
	addfav(fmt.Sprintf("%s,bobdog@twitter", u.Username), true, false, idUI, tc)
	if len(listfav(tc)) != 4 {
		t.Errorf("favorites len: %d, expected 4", len(listfav(tc)))
	}
	if idUI.DisplayTLFCount != 0 {
		t.Errorf("DisplayTLFCount: %d, expected 0", idUI.DisplayTLFCount)
	}

	idUI = &FakeIdentifyUI{}
	// Make sure ui for displaying tlf + invite not called for non-social
	// assertion TLF.
	addfav(fmt.Sprintf("%s,t_alice", u.Username), true, true, idUI, tc)
	if len(listfav(tc)) != 5 {
		t.Errorf("favorites len: %d, expected 5", len(listfav(tc)))
	}
	if idUI.DisplayTLFCount != 0 {
		t.Errorf("DisplayTLFCount: %d, expected 0", idUI.DisplayTLFCount)
	}

	idUI = &FakeIdentifyUI{}
	// Test adding a public favorite with SBS social assertion
	addfav(fmt.Sprintf("%s,bobdog@twitter", u.Username), false, true, idUI, tc)
	if len(listfav(tc)) != 6 {
		t.Errorf("favorites len: %d, expected 6", len(listfav(tc)))
	}
	if idUI.DisplayTLFCount != 1 {
		t.Errorf("DisplayTLFCount: %d, expected 1", idUI.DisplayTLFCount)
	}
	if idUI.DisplayTLFArg.IsPrivate {
		t.Errorf("DisplayTLFArg.IsPrivate set on a public folder")
	}
}

func TestFavoriteIgnore(t *testing.T) {
	tc := SetupEngineTest(t, "template")
	defer tc.Cleanup()
	CreateAndSignupFakeUser(tc, "fav")

	idUI := &FakeIdentifyUI{}
	addfav("t_alice,t_bob", true, true, idUI, tc)
	addfav("t_alice,t_charlie", true, true, idUI, tc)
	if len(listfav(tc)) != 4 {
		t.Errorf("favorites len: %d, expected 4", len(listfav(tc)))
	}
	rmfav("t_alice,t_bob", true, tc)
	if len(listfav(tc)) != 3 {
		t.Errorf("favorites len: %d, expected 3", len(listfav(tc)))
	}
	if listfav(tc)[0].Name != "t_alice,t_charlie" {
		t.Errorf("favorites entry: %q, expected %q", listfav(tc)[0].Name, "t_alice,t_charlie")
	}
}

func TestFavoriteList(t *testing.T) {
	tc := SetupEngineTest(t, "template")
	defer tc.Cleanup()
	CreateAndSignupFakeUser(tc, "fav")

	idUI := &FakeIdentifyUI{}
	addfav("t_alice,t_charlie", true, true, idUI, tc)
	addfav("t_alice,t_bob", true, true, idUI, tc)

	ctx := &Context{}
	eng := NewFavoriteList(tc.G)
	if err := RunEngine(eng, ctx); err != nil {
		t.Fatal(err)
	}
	favs := eng.Result().FavoriteFolders
	if len(favs) != 4 {
		t.Fatalf("num favs: %d, expected 4", len(favs))
	}
	if favs[0].Name != "t_alice,t_bob" {
		t.Errorf("fav 0: %q, expected t_alice,t_bob", favs[0].Name)
	}
	if favs[1].Name != "t_alice,t_charlie" {
		t.Errorf("fav 1: %q, expected t_alice,t_charlie", favs[1].Name)
	}
}

func addfav(name string, private, created bool, idUI libkb.IdentifyUI, tc libkb.TestContext) {
	ctx := &Context{
		IdentifyUI: idUI,
	}
	arg := keybase1.FavoriteAddArg{
		Folder: keybase1.Folder{Name: name, Private: private, Created: created},
	}
	eng := NewFavoriteAdd(&arg, tc.G)
	err := RunEngine(eng, ctx)
	if err != nil {
		tc.T.Fatal(err)
	}
	eng.Wait()
}

func rmfav(name string, private bool, tc libkb.TestContext) {
	ctx := &Context{}
	arg := keybase1.FavoriteIgnoreArg{
		Folder: keybase1.Folder{Name: name, Private: private},
	}
	eng := NewFavoriteIgnore(&arg, tc.G)
	err := RunEngine(eng, ctx)
	if err != nil {
		tc.T.Fatal(err)
	}
}

func listfav(tc libkb.TestContext) []keybase1.Folder {
	ctx := &Context{}
	eng := NewFavoriteList(tc.G)
	err := RunEngine(eng, ctx)
	if err != nil {
		tc.T.Fatal(err)
	}
	return eng.Result().FavoriteFolders
}

// func defaultFaves(un string) []keybase1.Folder {
// 	return []keybase1.Folder{
// 		keybase1.Folder{ name : un, private : false },
// 		keybase1.Folder{ name : un, private : true },
// 	}
// }
//
// func hasDupes(folders []keybase1.Folder) {
// 	tab := make(map[string]bool)
// 	for folder := range folders {
//
// 	}
// }
//
// func compareFaves(a []keybase1.Folder, b.keybase1.Folder[]) {
//
// }
<commit_msg>better favorites tests -- do set equality on the results<commit_after>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package engine

import (
	"fmt"
	"sort"
	"strings"
	"testing"

	"github.com/keybase/client/go/libkb"
	keybase1 "github.com/keybase/client/go/protocol"
)

func TestFavoriteAdd(t *testing.T) {
	tc := SetupEngineTest(t, "template")
	defer tc.Cleanup()
	u := CreateAndSignupFakeUser(tc, "fav")
	expectedFaves := newFavorites(u.Username)

	idUI := &FakeIdentifyUI{}
	addfav("t_alice,t_bob", true, true, idUI, tc, expectedFaves)
	if !listfav(tc).Equal(*expectedFaves) {
		t.Errorf("bad favorites")
	}

	// Add the same share again. The number shouldn't change.
	addfav("t_alice,t_bob", true, true, idUI, tc, nil)
	if !listfav(tc).Equal(*expectedFaves) {
		t.Errorf("bad favorites")
	}

	// Add a public share of the same name, make sure both are represented.
	addfav("t_alice,t_bob", false, true, idUI, tc, expectedFaves)
	if !listfav(tc).Equal(*expectedFaves) {
		t.Errorf("bad favorites")
	}
}

// Test adding a favorite with a social assertion.
// Sharing before signup, social assertion user doesn't
// exist yet.
func TestFavoriteAddSocial(t *testing.T) {
	tc := SetupEngineTest(t, "template")
	defer tc.Cleanup()
	u := CreateAndSignupFakeUser(tc, "fav")
	expectedFaves := newFavorites(u.Username)

	idUI := &FakeIdentifyUI{}
	addfav(fmt.Sprintf("bob@twitter,%s", u.Username), true, true, idUI, tc, expectedFaves)
	if !listfav(tc).Equal(*expectedFaves) {
		t.Errorf("bad favorites")
	}

	if idUI.DisplayTLFCount != 1 {
		t.Errorf("DisplayTLFCount: %d, expected 1", idUI.DisplayTLFCount)
	}
	// There's no way to give invites to a user via API, so the
	// only case we can test automatically is the user being
	// out of invites.
	if !idUI.DisplayTLFArg.Throttled {
		t.Errorf("DisplayTLFArg.Throttled not set, expected it to be since user has no invites.")
	}
	if !idUI.DisplayTLFArg.IsPrivate {
		t.Errorf("DisplayTLFArg.IsPrivate not set on a private folder")
	}

	idUI = &FakeIdentifyUI{}
	// Test adding a favorite when not the creator.  Should not call ui for
	// displaying tlf + invite.
	// created flag == false
	addfav(fmt.Sprintf("bobdog@twitter,%s", u.Username), true, false, idUI, tc, expectedFaves)
	if newFaves := listfav(tc); !newFaves.Equal(*expectedFaves) {
		t.Errorf("bad favorites: %s != %s", newFaves, expectedFaves)
	}
	if idUI.DisplayTLFCount != 0 {
		t.Errorf("DisplayTLFCount: %d, expected 0", idUI.DisplayTLFCount)
	}

	idUI = &FakeIdentifyUI{}
	// Make sure ui for displaying tlf + invite not called for non-social
	// assertion TLF.
	addfav(fmt.Sprintf("%s,t_alice", u.Username), true, true, idUI, tc, expectedFaves)
	if newFaves := listfav(tc); !newFaves.Equal(*expectedFaves) {
		t.Errorf("bad favorites: %s != %s", newFaves, expectedFaves)
	}
	if idUI.DisplayTLFCount != 0 {
		t.Errorf("DisplayTLFCount: %d, expected 0", idUI.DisplayTLFCount)
	}

	idUI = &FakeIdentifyUI{}
	// Test adding a public favorite with SBS social assertion
	addfav(fmt.Sprintf("bobdog@twitter,%s", u.Username), false, true, idUI, tc, expectedFaves)
	if newFaves := listfav(tc); !newFaves.Equal(*expectedFaves) {
		t.Errorf("bad favorites: %s != %s", newFaves, expectedFaves)
	}
	if idUI.DisplayTLFCount != 1 {
		t.Errorf("DisplayTLFCount: %d, expected 1", idUI.DisplayTLFCount)
	}
	if idUI.DisplayTLFArg.IsPrivate {
		t.Errorf("DisplayTLFArg.IsPrivate set on a public folder")
	}
}

func TestFavoriteIgnore(t *testing.T) {
	tc := SetupEngineTest(t, "template")
	defer tc.Cleanup()
	u := CreateAndSignupFakeUser(tc, "fav")

	expectedFaves := newFavorites(u.Username)

	idUI := &FakeIdentifyUI{}
	addfav("t_alice,t_bob", true, true, idUI, tc, expectedFaves)
	addfav("t_alice,t_charlie", true, true, idUI, tc, expectedFaves)
	if !listfav(tc).Equal(*expectedFaves) {
		t.Errorf("bad favorites")
	}
	rmfav("t_alice,t_bob", true, tc, expectedFaves)
	if !listfav(tc).Equal(*expectedFaves) {
		t.Errorf("bad favorites")
	}
}

func TestFavoriteList(t *testing.T) {
	tc := SetupEngineTest(t, "template")
	defer tc.Cleanup()
	u := CreateAndSignupFakeUser(tc, "fav")
	expectedFaves := newFavorites(u.Username)

	idUI := &FakeIdentifyUI{}
	addfav("t_alice,t_charlie", true, true, idUI, tc, expectedFaves)
	addfav("t_alice,t_bob", true, true, idUI, tc, expectedFaves)

	ctx := &Context{}
	eng := NewFavoriteList(tc.G)
	if err := RunEngine(eng, ctx); err != nil {
		t.Fatal(err)
	}
	favs := eng.Result().FavoriteFolders
	if !newFavoritesFromServer(favs).Equal(*expectedFaves) {
		t.Errorf("bad favorites")
	}
}

func addfav(name string, private, created bool, idUI libkb.IdentifyUI, tc libkb.TestContext, expectedFaves *favorites) {
	ctx := &Context{
		IdentifyUI: idUI,
	}
	arg := keybase1.FavoriteAddArg{
		Folder: keybase1.Folder{Name: name, Private: private, Created: created},
	}
	eng := NewFavoriteAdd(&arg, tc.G)
	err := RunEngine(eng, ctx)
	if err != nil {
		tc.T.Fatal(err)
	}
	eng.Wait()
	if expectedFaves != nil {
		expectedFaves.Push(keybase1.Folder{Name: name, Private: private})
	}
}

func rmfav(name string, private bool, tc libkb.TestContext, expectedFaves *favorites) {
	ctx := &Context{}
	arg := keybase1.FavoriteIgnoreArg{
		Folder: keybase1.Folder{Name: name, Private: private},
	}
	eng := NewFavoriteIgnore(&arg, tc.G)
	err := RunEngine(eng, ctx)
	if err != nil {
		tc.T.Fatal(err)
	}
	if expectedFaves != nil {
		expectedFaves.Remove(keybase1.Folder{Name: name, Private: private})
	}
}

func listfav(tc libkb.TestContext) *favorites {
	ctx := &Context{}
	eng := NewFavoriteList(tc.G)
	err := RunEngine(eng, ctx)
	if err != nil {
		tc.T.Fatal(err)
	}
	return newFavoritesFromServer(eng.Result().FavoriteFolders)
}

type favorites struct {
	m map[string]bool
}

func newFavorites(un string) *favorites {
	ret := &favorites{
		m: make(map[string]bool),
	}
	for _, f := range defaultFaves(un) {
		ret.Push(f)
	}
	return ret
}

func newFavoritesFromServer(v []keybase1.Folder) *favorites {
	ret := &favorites{
		m: make(map[string]bool),
	}
	for _, f := range v {
		ret.Push(f)
	}
	return ret
}

func (v *favorites) Push(f keybase1.Folder) {
	k := makeKey(f)
	if !v.m[k] {
		v.m[k] = true
	}
}

func (v *favorites) Remove(f keybase1.Folder) {
	delete(v.m, makeKey(f))
}

func (v favorites) Equal(b favorites) bool {
	for k := range v.m {
		if !b.m[k] {
			return false
		}
	}
	for k := range b.m {
		if !v.m[k] {
			return false
		}
	}
	return true
}

func makeKey(f keybase1.Folder) string {
	return fmt.Sprintf("%s:%v", f.Name, f.Private)
}

func defaultFaves(un string) []keybase1.Folder {
	return []keybase1.Folder{
		keybase1.Folder{Name: un, Private: false},
		keybase1.Folder{Name: un, Private: true},
	}
}

func (v *favorites) String() string {
	var s []string
	for f := range v.m {
		s = append(s, f)
	}
	sort.Strings(s)
	return strings.Join(s, ";")
}
<|endoftext|>