<commit_before>// Copyright 2015 Peter Goetz
//
// 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 pegomock

import (
	"fmt"
	"reflect"
	"testing"

	"github.com/petergtz/pegomock/internal/verify"
)

var GlobalFailHandler FailHandler

func RegisterMockFailHandler(handler FailHandler) {
	GlobalFailHandler = handler
}
func RegisterMockTestingT(t *testing.T) {
	RegisterMockFailHandler(BuildTestingTGomegaFailHandler(t))
}

var lastInvocation *invocation
var globalArgMatchers Matchers

func RegisterMatcher(matcher Matcher) {
	globalArgMatchers.append(matcher)
}

type invocation struct {
	genericMock *GenericMock
	MethodName  string
	Params      []Param
	ReturnTypes []reflect.Type
}

type GenericMock struct {
	mockedMethods map[string]*mockedMethod
}

func (genericMock *GenericMock) Invoke(methodName string, params []Param, returnTypes []reflect.Type) ReturnValues {
	lastInvocation = &invocation{
		genericMock: genericMock,
		MethodName:  methodName,
		Params:      params,
		ReturnTypes: returnTypes,
	}
	return genericMock.getOrCreateMockedMethod(methodName).Invoke(params)
}

func (genericMock *GenericMock) stub(methodName string, paramMatchers []Matcher, returnValues ReturnValues) {
	genericMock.stubWithCallback(methodName, paramMatchers, func([]Param) ReturnValues { return returnValues })
}

func (genericMock *GenericMock) stubWithCallback(methodName string, paramMatchers []Matcher, callback func([]Param) ReturnValues) {
	genericMock.getOrCreateMockedMethod(methodName).stub(paramMatchers, callback)
}

func (genericMock *GenericMock) getOrCreateMockedMethod(methodName string) *mockedMethod {
	if _, ok := genericMock.mockedMethods[methodName]; !ok {
		genericMock.mockedMethods[methodName] = &mockedMethod{name: methodName}
	}
	return genericMock.mockedMethods[methodName]
}

func (genericMock *GenericMock) reset(methodName string, paramMatchers []Matcher) {
	genericMock.getOrCreateMockedMethod(methodName).reset(paramMatchers)
}

func (genericMock *GenericMock) Verify(
	inOrderContext *InOrderContext,
	invocationCountMatcher Matcher,
	methodName string,
	params []Param) []MethodInvocation {
	if GlobalFailHandler == nil {
		panic("No GlobalFailHandler set. Please use either RegisterMockFailHandler or RegisterMockTestingT to set a fail handler.")
	}
	defer func() { globalArgMatchers = nil }() // We don't want a panic somewhere during verification screw our global argMatchers

	if len(globalArgMatchers) != 0 {
		verifyArgMatcherUse(globalArgMatchers, params)
	}

	methodInvocations := genericMock.methodInvocations(methodName, params, globalArgMatchers)
	if inOrderContext != nil {
		for _, methodInvocation := range methodInvocations {
			if methodInvocation.orderingInvocationNumber <= inOrderContext.invocationCounter {
				GlobalFailHandler(fmt.Sprintf("Expected function call \"%v\" with params %v before function call \"%v\" with params %v",
					methodName, params, inOrderContext.lastInvokedMethodName, inOrderContext.lastInvokedMethodParams))
			}
			inOrderContext.invocationCounter = methodInvocation.orderingInvocationNumber
			inOrderContext.lastInvokedMethodName = methodName
			inOrderContext.lastInvokedMethodParams = params
		}
	}
	if !invocationCountMatcher.Matches(len(methodInvocations)) {
		if len(globalArgMatchers) == 0 {
			GlobalFailHandler(fmt.Sprintf(
				"Mock invocation count for method \"%s\" with params %v does not match expectation.\n\n\t%v",
				methodName, params, invocationCountMatcher.FailureMessage()))
		} else {
			GlobalFailHandler(fmt.Sprintf(
				"Mock invocation count for method \"%s\" with params %v does not match expectation.\n\n\t%v",
				methodName, globalArgMatchers, invocationCountMatcher.FailureMessage()))
		}
	}
	return methodInvocations
}

func (genericMock *GenericMock) GetInvocationParams(methodInvocations []MethodInvocation) [][]Param {
	if len(methodInvocations) == 0 {
		return nil
	}
	result := make([][]Param, len(methodInvocations[len(methodInvocations)-1].params))
	for i, invocation := range methodInvocations {
		for u, param := range invocation.params {
			if result[u] == nil {
				result[u] = make([]Param, len(methodInvocations))
			}
			result[u][i] = param
		}
	}
	return result
}

func (genericMock *GenericMock) methodInvocations(methodName string, params []Param, matchers []Matcher) []MethodInvocation {
	var invocations []MethodInvocation
	if _, exists := genericMock.mockedMethods[methodName]; exists {
		for _, invocation := range genericMock.mockedMethods[methodName].invocations {
			if (len(matchers) != 0 && Matchers(matchers).Matches(invocation.params)) ||
				(reflect.DeepEqual(params, invocation.params) ||
					(len(params) == 0 && len(invocation.params) == 0)) {
				invocations = append(invocations, invocation)
			}
		}
	}
	return invocations
}

type mockedMethod struct {
	name        string
	invocations []MethodInvocation
	stubbings   Stubbings
}

func (method *mockedMethod) Invoke(params []Param) ReturnValues {
	method.invocations = append(method.invocations, MethodInvocation{params, globalInvocationCounter.nextNumber()})
	stubbing := method.stubbings.find(params)
	if stubbing == nil {
		return ReturnValues{}
	}
	return stubbing.Invoke(params)
}

func (method *mockedMethod) stub(paramMatchers Matchers, callback func([]Param) ReturnValues) {
	stubbing := method.stubbings.findByMatchers(paramMatchers)
	if stubbing == nil {
		stubbing = &Stubbing{paramMatchers: paramMatchers}
		method.stubbings = append(method.stubbings, stubbing)
	}
	stubbing.callbackSequence = append(stubbing.callbackSequence, callback)
}

func (method *mockedMethod) removeLastInvocation() {
	method.invocations = method.invocations[:len(method.invocations)-1]
}

func (method *mockedMethod) reset(paramMatchers Matchers) {
	method.stubbings.removeByMatchers(paramMatchers)
}

type Counter struct {
	count int
}

func (counter *Counter) nextNumber() (nextNumber int) {
	nextNumber = counter.count
	counter.count++
	return
}

var globalInvocationCounter Counter

type MethodInvocation struct {
	params                   []Param
	orderingInvocationNumber int
}

type Stubbings []*Stubbing

func (stubbings Stubbings) find(params []Param) *Stubbing {
	for i := len(stubbings) - 1; i >= 0; i-- {
		if stubbings[i].paramMatchers.Matches(params) {
			return stubbings[i]
		}
	}
	return nil
}

func (stubbings Stubbings) findByMatchers(paramMatchers Matchers) *Stubbing {
	for _, stubbing := range stubbings {
		if matchersEqual(stubbing.paramMatchers, paramMatchers) {
			return stubbing
		}
	}
	return nil
}

func (stubbings *Stubbings) removeByMatchers(paramMatchers Matchers) {
	for i, stubbing := range *stubbings {
		if matchersEqual(stubbing.paramMatchers, paramMatchers) {
			*stubbings = append((*stubbings)[:i], (*stubbings)[i+1:]...)
		}
	}
}

func matchersEqual(a, b Matchers) bool {
	if len(a) != len(b) {
		return false
	}
	for i := range a {
		if !reflect.DeepEqual(a[i], b[i]) {
			return false
		}
	}
	return true
}

type Stubbing struct {
	paramMatchers    Matchers
	callbackSequence []func([]Param) ReturnValues
	sequencePointer  int
}

func (stubbing *Stubbing) Invoke(params []Param) ReturnValues {
	defer func() {
		if stubbing.sequencePointer < len(stubbing.callbackSequence)-1 {
			stubbing.sequencePointer++
		}
	}()
	return stubbing.callbackSequence[stubbing.sequencePointer](params)
}

type Matchers []Matcher

func (matchers Matchers) Matches(params []Param) bool {
	if len(matchers) != len(params) { // Technically, this is not an error. Variadic arguments can cause this
		return false
	}
	for i := range params {
		if !matchers[i].Matches(params[i]) {
			return false
		}
	}
	return true
}

func (matchers *Matchers) append(matcher Matcher) {
	*matchers = append(*matchers, matcher)
}

type ongoingStubbing struct {
	genericMock   *GenericMock
	MethodName    string
	ParamMatchers []Matcher
	returnTypes   []reflect.Type
}

func When(invocation ...interface{}) *ongoingStubbing {
	callIfIsFunc(invocation)
	verify.Argument(lastInvocation != nil,
		"When() requires an argument which has to be 'a method call on a mock'.")
	defer func() {
		lastInvocation = nil
		globalArgMatchers = nil
	}()
	lastInvocation.genericMock.mockedMethods[lastInvocation.MethodName].removeLastInvocation()

	paramMatchers := paramMatchersFromArgMatchersOrParams(globalArgMatchers, lastInvocation.Params)
	lastInvocation.genericMock.reset(lastInvocation.MethodName, paramMatchers)
	return &ongoingStubbing{
		genericMock:   lastInvocation.genericMock,
		MethodName:    lastInvocation.MethodName,
		ParamMatchers: paramMatchers,
		returnTypes:   lastInvocation.ReturnTypes,
	}
}

func callIfIsFunc(invocation []interface{}) {
	if len(invocation) == 1 {
		actualType := actualTypeOf(invocation[0])
		if actualType != nil && actualType.Kind() == reflect.Func && !reflect.ValueOf(invocation[0]).IsNil() {
			if !(actualType.NumIn() == 0 && actualType.NumOut() == 0) {
				panic("When using 'When' with function that does not return a value, " +
					"it expects a function with no arguments and no return value.")
			}
			reflect.ValueOf(invocation[0]).Call([]reflect.Value{})
		}
	}
}

// Deals with nils without panicking
func actualTypeOf(iface interface{}) reflect.Type {
	defer func() { recover() }()
	return reflect.TypeOf(iface)
}

func paramMatchersFromArgMatchersOrParams(argMatchers []Matcher, params []Param) []Matcher {
	if len(argMatchers) != 0 {
		verifyArgMatcherUse(argMatchers, params)
		return argMatchers
	}
	return transformParamsIntoEqMatchers(params)
}

func verifyArgMatcherUse(argMatchers []Matcher, params []Param) {
	verify.Argument(len(argMatchers) == len(params),
		"Invalid use of matchers!\n\n %v matchers expected, %v recorded.\n\n"+
			"This error may occur if matchers are combined with raw values:\n"+
			"    //incorrect:\n"+
			"    someFunc(AnyInt(), \"raw String\")\n"+
			"When using matchers, all arguments have to be provided by matchers.\n"+
			"For example:\n"+
			"    //correct:\n"+
			"    someFunc(AnyInt(), EqString(\"String by matcher\"))",
		len(params), len(argMatchers),
	)
}

func transformParamsIntoEqMatchers(params []Param) []Matcher {
	paramMatchers := make([]Matcher, len(params))
	for i, param := range params {
		paramMatchers[i] = &EqMatcher{Value: param}
	}
	return paramMatchers
}

var genericMocks = make(map[Mock]*GenericMock)

func GetGenericMockFrom(mock Mock) *GenericMock {
	if genericMocks[mock] == nil {
		genericMocks[mock] = &GenericMock{mockedMethods: make(map[string]*mockedMethod)}
	}
	return genericMocks[mock]
}

func (stubbing *ongoingStubbing) ThenReturn(values ...ReturnValue) *ongoingStubbing {
	checkAssignabilityOf(values, stubbing.returnTypes)
	stubbing.genericMock.stub(stubbing.MethodName, stubbing.ParamMatchers, values)
	return stubbing
}

func checkAssignabilityOf(stubbedReturnValues []ReturnValue, expectedReturnTypes []reflect.Type) {
	verify.Argument(len(stubbedReturnValues) == len(expectedReturnTypes),
		"Different number of return values")
	for i := range stubbedReturnValues {
		if stubbedReturnValues[i] == nil {
			switch expectedReturnTypes[i].Kind() {
			case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint,
				reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Float32,
				reflect.Float64, reflect.Complex64, reflect.Complex128, reflect.Array, reflect.String,
				reflect.Struct:
				panic("Return value 'nil' not assignable to return type " + expectedReturnTypes[i].Kind().String())
			}
		} else {
			verify.Argument(reflect.TypeOf(stubbedReturnValues[i]).AssignableTo(expectedReturnTypes[i]),
				"Return value of type %T not assignable to return type %v", stubbedReturnValues[i], expectedReturnTypes[i])
		}
	}
}

func (stubbing *ongoingStubbing) ThenPanic(v interface{}) *ongoingStubbing {
	stubbing.genericMock.stubWithCallback(
		stubbing.MethodName,
		stubbing.ParamMatchers,
		func([]Param) ReturnValues { panic(v) })
	return stubbing
}

func (stubbing *ongoingStubbing) Then(callback func([]Param) ReturnValues) *ongoingStubbing {
	stubbing.genericMock.stubWithCallback(
		stubbing.MethodName,
		stubbing.ParamMatchers,
		callback)
	return stubbing
}

type InOrderContext struct {
	invocationCounter       int
	lastInvokedMethodName   string
	lastInvokedMethodParams []Param
}

// Matcher ... it is guaranteed that FailureMessage will always be called after Matches
// so an implementation can save state
type Matcher interface {
	Matches(param Param) bool
	FailureMessage() string
	fmt.Stringer
}
<commit_msg>Fix in-order-verification when it is the very verification in the test suite<commit_after>// Copyright 2015 Peter Goetz
//
// 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 pegomock

import (
	"fmt"
	"reflect"
	"testing"

	"github.com/petergtz/pegomock/internal/verify"
)

var GlobalFailHandler FailHandler

func RegisterMockFailHandler(handler FailHandler) {
	GlobalFailHandler = handler
}
func RegisterMockTestingT(t *testing.T) {
	RegisterMockFailHandler(BuildTestingTGomegaFailHandler(t))
}

var lastInvocation *invocation
var globalArgMatchers Matchers

func RegisterMatcher(matcher Matcher) {
	globalArgMatchers.append(matcher)
}

type invocation struct {
	genericMock *GenericMock
	MethodName  string
	Params      []Param
	ReturnTypes []reflect.Type
}

type GenericMock struct {
	mockedMethods map[string]*mockedMethod
}

func (genericMock *GenericMock) Invoke(methodName string, params []Param, returnTypes []reflect.Type) ReturnValues {
	lastInvocation = &invocation{
		genericMock: genericMock,
		MethodName:  methodName,
		Params:      params,
		ReturnTypes: returnTypes,
	}
	return genericMock.getOrCreateMockedMethod(methodName).Invoke(params)
}

func (genericMock *GenericMock) stub(methodName string, paramMatchers []Matcher, returnValues ReturnValues) {
	genericMock.stubWithCallback(methodName, paramMatchers, func([]Param) ReturnValues { return returnValues })
}

func (genericMock *GenericMock) stubWithCallback(methodName string, paramMatchers []Matcher, callback func([]Param) ReturnValues) {
	genericMock.getOrCreateMockedMethod(methodName).stub(paramMatchers, callback)
}

func (genericMock *GenericMock) getOrCreateMockedMethod(methodName string) *mockedMethod {
	if _, ok := genericMock.mockedMethods[methodName]; !ok {
		genericMock.mockedMethods[methodName] = &mockedMethod{name: methodName}
	}
	return genericMock.mockedMethods[methodName]
}

func (genericMock *GenericMock) reset(methodName string, paramMatchers []Matcher) {
	genericMock.getOrCreateMockedMethod(methodName).reset(paramMatchers)
}

func (genericMock *GenericMock) Verify(
	inOrderContext *InOrderContext,
	invocationCountMatcher Matcher,
	methodName string,
	params []Param) []MethodInvocation {
	if GlobalFailHandler == nil {
		panic("No GlobalFailHandler set. Please use either RegisterMockFailHandler or RegisterMockTestingT to set a fail handler.")
	}
	defer func() { globalArgMatchers = nil }() // We don't want a panic somewhere during verification screw our global argMatchers

	if len(globalArgMatchers) != 0 {
		verifyArgMatcherUse(globalArgMatchers, params)
	}

	methodInvocations := genericMock.methodInvocations(methodName, params, globalArgMatchers)
	if inOrderContext != nil {
		for _, methodInvocation := range methodInvocations {
			if methodInvocation.orderingInvocationNumber <= inOrderContext.invocationCounter {
				GlobalFailHandler(fmt.Sprintf("Expected function call \"%v\" with params %v before function call \"%v\" with params %v",
					methodName, params, inOrderContext.lastInvokedMethodName, inOrderContext.lastInvokedMethodParams))
			}
			inOrderContext.invocationCounter = methodInvocation.orderingInvocationNumber
			inOrderContext.lastInvokedMethodName = methodName
			inOrderContext.lastInvokedMethodParams = params
		}
	}
	if !invocationCountMatcher.Matches(len(methodInvocations)) {
		if len(globalArgMatchers) == 0 {
			GlobalFailHandler(fmt.Sprintf(
				"Mock invocation count for method \"%s\" with params %v does not match expectation.\n\n\t%v",
				methodName, params, invocationCountMatcher.FailureMessage()))
		} else {
			GlobalFailHandler(fmt.Sprintf(
				"Mock invocation count for method \"%s\" with params %v does not match expectation.\n\n\t%v",
				methodName, globalArgMatchers, invocationCountMatcher.FailureMessage()))
		}
	}
	return methodInvocations
}

func (genericMock *GenericMock) GetInvocationParams(methodInvocations []MethodInvocation) [][]Param {
	if len(methodInvocations) == 0 {
		return nil
	}
	result := make([][]Param, len(methodInvocations[len(methodInvocations)-1].params))
	for i, invocation := range methodInvocations {
		for u, param := range invocation.params {
			if result[u] == nil {
				result[u] = make([]Param, len(methodInvocations))
			}
			result[u][i] = param
		}
	}
	return result
}

func (genericMock *GenericMock) methodInvocations(methodName string, params []Param, matchers []Matcher) []MethodInvocation {
	var invocations []MethodInvocation
	if _, exists := genericMock.mockedMethods[methodName]; exists {
		for _, invocation := range genericMock.mockedMethods[methodName].invocations {
			if (len(matchers) != 0 && Matchers(matchers).Matches(invocation.params)) ||
				(reflect.DeepEqual(params, invocation.params) ||
					(len(params) == 0 && len(invocation.params) == 0)) {
				invocations = append(invocations, invocation)
			}
		}
	}
	return invocations
}

type mockedMethod struct {
	name        string
	invocations []MethodInvocation
	stubbings   Stubbings
}

func (method *mockedMethod) Invoke(params []Param) ReturnValues {
	method.invocations = append(method.invocations, MethodInvocation{params, globalInvocationCounter.nextNumber()})
	stubbing := method.stubbings.find(params)
	if stubbing == nil {
		return ReturnValues{}
	}
	return stubbing.Invoke(params)
}

func (method *mockedMethod) stub(paramMatchers Matchers, callback func([]Param) ReturnValues) {
	stubbing := method.stubbings.findByMatchers(paramMatchers)
	if stubbing == nil {
		stubbing = &Stubbing{paramMatchers: paramMatchers}
		method.stubbings = append(method.stubbings, stubbing)
	}
	stubbing.callbackSequence = append(stubbing.callbackSequence, callback)
}

func (method *mockedMethod) removeLastInvocation() {
	method.invocations = method.invocations[:len(method.invocations)-1]
}

func (method *mockedMethod) reset(paramMatchers Matchers) {
	method.stubbings.removeByMatchers(paramMatchers)
}

type Counter struct {
	count int
}

func (counter *Counter) nextNumber() (nextNumber int) {
	nextNumber = counter.count
	counter.count++
	return
}

var globalInvocationCounter = Counter{1}

type MethodInvocation struct {
	params                   []Param
	orderingInvocationNumber int
}

type Stubbings []*Stubbing

func (stubbings Stubbings) find(params []Param) *Stubbing {
	for i := len(stubbings) - 1; i >= 0; i-- {
		if stubbings[i].paramMatchers.Matches(params) {
			return stubbings[i]
		}
	}
	return nil
}

func (stubbings Stubbings) findByMatchers(paramMatchers Matchers) *Stubbing {
	for _, stubbing := range stubbings {
		if matchersEqual(stubbing.paramMatchers, paramMatchers) {
			return stubbing
		}
	}
	return nil
}

func (stubbings *Stubbings) removeByMatchers(paramMatchers Matchers) {
	for i, stubbing := range *stubbings {
		if matchersEqual(stubbing.paramMatchers, paramMatchers) {
			*stubbings = append((*stubbings)[:i], (*stubbings)[i+1:]...)
		}
	}
}

func matchersEqual(a, b Matchers) bool {
	if len(a) != len(b) {
		return false
	}
	for i := range a {
		if !reflect.DeepEqual(a[i], b[i]) {
			return false
		}
	}
	return true
}

type Stubbing struct {
	paramMatchers    Matchers
	callbackSequence []func([]Param) ReturnValues
	sequencePointer  int
}

func (stubbing *Stubbing) Invoke(params []Param) ReturnValues {
	defer func() {
		if stubbing.sequencePointer < len(stubbing.callbackSequence)-1 {
			stubbing.sequencePointer++
		}
	}()
	return stubbing.callbackSequence[stubbing.sequencePointer](params)
}

type Matchers []Matcher

func (matchers Matchers) Matches(params []Param) bool {
	if len(matchers) != len(params) { // Technically, this is not an error. Variadic arguments can cause this
		return false
	}
	for i := range params {
		if !matchers[i].Matches(params[i]) {
			return false
		}
	}
	return true
}

func (matchers *Matchers) append(matcher Matcher) {
	*matchers = append(*matchers, matcher)
}

type ongoingStubbing struct {
	genericMock   *GenericMock
	MethodName    string
	ParamMatchers []Matcher
	returnTypes   []reflect.Type
}

func When(invocation ...interface{}) *ongoingStubbing {
	callIfIsFunc(invocation)
	verify.Argument(lastInvocation != nil,
		"When() requires an argument which has to be 'a method call on a mock'.")
	defer func() {
		lastInvocation = nil
		globalArgMatchers = nil
	}()
	lastInvocation.genericMock.mockedMethods[lastInvocation.MethodName].removeLastInvocation()

	paramMatchers := paramMatchersFromArgMatchersOrParams(globalArgMatchers, lastInvocation.Params)
	lastInvocation.genericMock.reset(lastInvocation.MethodName, paramMatchers)
	return &ongoingStubbing{
		genericMock:   lastInvocation.genericMock,
		MethodName:    lastInvocation.MethodName,
		ParamMatchers: paramMatchers,
		returnTypes:   lastInvocation.ReturnTypes,
	}
}

func callIfIsFunc(invocation []interface{}) {
	if len(invocation) == 1 {
		actualType := actualTypeOf(invocation[0])
		if actualType != nil && actualType.Kind() == reflect.Func && !reflect.ValueOf(invocation[0]).IsNil() {
			if !(actualType.NumIn() == 0 && actualType.NumOut() == 0) {
				panic("When using 'When' with function that does not return a value, " +
					"it expects a function with no arguments and no return value.")
			}
			reflect.ValueOf(invocation[0]).Call([]reflect.Value{})
		}
	}
}

// Deals with nils without panicking
func actualTypeOf(iface interface{}) reflect.Type {
	defer func() { recover() }()
	return reflect.TypeOf(iface)
}

func paramMatchersFromArgMatchersOrParams(argMatchers []Matcher, params []Param) []Matcher {
	if len(argMatchers) != 0 {
		verifyArgMatcherUse(argMatchers, params)
		return argMatchers
	}
	return transformParamsIntoEqMatchers(params)
}

func verifyArgMatcherUse(argMatchers []Matcher, params []Param) {
	verify.Argument(len(argMatchers) == len(params),
		"Invalid use of matchers!\n\n %v matchers expected, %v recorded.\n\n"+
			"This error may occur if matchers are combined with raw values:\n"+
			"    //incorrect:\n"+
			"    someFunc(AnyInt(), \"raw String\")\n"+
			"When using matchers, all arguments have to be provided by matchers.\n"+
			"For example:\n"+
			"    //correct:\n"+
			"    someFunc(AnyInt(), EqString(\"String by matcher\"))",
		len(params), len(argMatchers),
	)
}

func transformParamsIntoEqMatchers(params []Param) []Matcher {
	paramMatchers := make([]Matcher, len(params))
	for i, param := range params {
		paramMatchers[i] = &EqMatcher{Value: param}
	}
	return paramMatchers
}

var genericMocks = make(map[Mock]*GenericMock)

func GetGenericMockFrom(mock Mock) *GenericMock {
	if genericMocks[mock] == nil {
		genericMocks[mock] = &GenericMock{mockedMethods: make(map[string]*mockedMethod)}
	}
	return genericMocks[mock]
}

func (stubbing *ongoingStubbing) ThenReturn(values ...ReturnValue) *ongoingStubbing {
	checkAssignabilityOf(values, stubbing.returnTypes)
	stubbing.genericMock.stub(stubbing.MethodName, stubbing.ParamMatchers, values)
	return stubbing
}

func checkAssignabilityOf(stubbedReturnValues []ReturnValue, expectedReturnTypes []reflect.Type) {
	verify.Argument(len(stubbedReturnValues) == len(expectedReturnTypes),
		"Different number of return values")
	for i := range stubbedReturnValues {
		if stubbedReturnValues[i] == nil {
			switch expectedReturnTypes[i].Kind() {
			case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint,
				reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Float32,
				reflect.Float64, reflect.Complex64, reflect.Complex128, reflect.Array, reflect.String,
				reflect.Struct:
				panic("Return value 'nil' not assignable to return type " + expectedReturnTypes[i].Kind().String())
			}
		} else {
			verify.Argument(reflect.TypeOf(stubbedReturnValues[i]).AssignableTo(expectedReturnTypes[i]),
				"Return value of type %T not assignable to return type %v", stubbedReturnValues[i], expectedReturnTypes[i])
		}
	}
}

func (stubbing *ongoingStubbing) ThenPanic(v interface{}) *ongoingStubbing {
	stubbing.genericMock.stubWithCallback(
		stubbing.MethodName,
		stubbing.ParamMatchers,
		func([]Param) ReturnValues { panic(v) })
	return stubbing
}

func (stubbing *ongoingStubbing) Then(callback func([]Param) ReturnValues) *ongoingStubbing {
	stubbing.genericMock.stubWithCallback(
		stubbing.MethodName,
		stubbing.ParamMatchers,
		callback)
	return stubbing
}

type InOrderContext struct {
	invocationCounter       int
	lastInvokedMethodName   string
	lastInvokedMethodParams []Param
}

// Matcher ... it is guaranteed that FailureMessage will always be called after Matches
// so an implementation can save state
type Matcher interface {
	Matches(param Param) bool
	FailureMessage() string
	fmt.Stringer
}
<|endoftext|>


<commit_before>package po

import (
    "go/ast"
    "go/token"
    prsGo "go/parser"
    prsTmpl "github.com/Sam-Izdat/pogo/deps/template/parse"
    spec "github.com/Sam-Izdat/pogo/gtspec"
    "path/filepath"
    "io/ioutil"
    "strconv"
    "strings"
    "os"
)

var gf, ngf, pgf, npgf, lDelim, rDelim string
var cfgFN = "POGO.toml"

func init() {
    gf = o.Parsing.FuncG            // "gettext" function
    ngf = o.Parsing.FuncNG          // "ngettext" function
    pgf = o.Parsing.FuncPG          // "pgettext" function
    npgf = o.Parsing.FuncNPG        // "npgettext" function
    lDelim = o.Parsing.DelimL
    rDelim = o.Parsing.DelimR
}

func ScanGo(path string) (res []spec.Msg) {
    filepath.Walk(path, func(fp string, fi os.FileInfo, err error) error {
        if err != nil {
            return nil
        }
        if !!fi.IsDir() {
            switch fi.Name() {
            case ".", "..":
                return nil
            default:
                tmp := scanGoDir(fp)
                if tmp != nil { res = append(res, tmp...) }
            }
        }
        return nil
    })
    prepMsg(&res)
    return
}

func scanGoDir(path string) (res []spec.Msg) {
    // skip any subdirectory with its own POGO.toml config
    conf := filepath.Join(path, cfgFN)
    if _, err := os.Stat(conf); err == nil && path != o.General.DirProject {
        return nil
    }

    fset := token.NewFileSet()
    pkgs, err := prsGo.ParseDir(fset, path, nil, 0)
    if err != nil { panic(err) }
    for _, pkg := range pkgs {
        for fn, f := range pkg.Files {
            ast.Inspect(f, func(n ast.Node) bool {
                var funcName string
                switch x := n.(type) {
                case *ast.CallExpr:
                    switch y := x.Fun.(type) {
                    case *ast.Ident:            // function call
                        funcName = y.Name
                    case *ast.SelectorExpr:     // method call
                        funcName = y.Sel.Name
                    }
                    switch funcName {
                    case gf, ngf, pgf, npgf:    // do nothing
                    default: return true
                    }
                    msgs := []spec.Msg{}
                    for k, arg := range x.Args {
                        switch y := arg.(type){
                        case *ast.BasicLit:
                            linePos := fset.Position(y.ValuePos).Line
                            switch funcName {
                            case gf:       // just singular
                                msgs = append(msgs, spec.Msg{Filename: fn, Line: linePos, Id: y.Value})
                            case ngf:      // singular and plural
                                switch k { // 0 - singular, 1 - plural, subsequent ignored
                                case 0:
                                    msgs = append(msgs, spec.Msg{Filename: fn, Line: linePos, Id: y.Value})
                                case 1:
                                    msgs[0].IdPlural = y.Value
                                }
                            case pgf:      // context and text
                                switch k { // 0 - context, 1 - text, subsequent ignored
                                case 0:
                                    msgs = append(msgs, spec.Msg{Filename: fn, Line: linePos, Ctxt: y.Value})
                                case 1:
                                    msgs[0].Id = y.Value
                                }
                            case npgf:
                                switch k { // 0 - context, 1 - singular, 2 - plural, subsequent ignored
                                case 0:
                                    msgs = append(msgs, spec.Msg{Filename: fn, Line: linePos, Ctxt: y.Value})
                                case 1:
                                    msgs[0].Id = y.Value
                                case 2:
                                    msgs[0].IdPlural = y.Value
                                }
                            }
                        }
                    }
                    if len(msgs) > 0 { res = append(res, msgs...) }
                }
                return true
            })
        }
    }
    return
}

func ScanTmpl(path string) (res []spec.Msg) {
    filepath.Walk(path, func(fp string, fi os.FileInfo, err error) error {
        if err != nil {
            return nil
        }

        // skip any subdirectory with its own POGO.toml config
        dir := filepath.Base(fp)
        conf := filepath.Join(dir, cfgFN)
        if _, err := os.Stat(conf); err == nil && dir != o.General.DirProject {
            return nil
        }

        if !!fi.IsDir() {
            return nil
        }

        matched := false
        for _, ext := range o.Parsing.TmplExts{
            var err error
            matched, err = filepath.Match("*."+ext, fi.Name())
            if err != nil {
                panic(err)
            }
            if matched { break }
        }

        if matched {
            buf, err := ioutil.ReadFile(fp)
            if err != nil { panic(err) }
            tmpl := string(buf)
            res = append(res, scanTmplString(tmpl)...)
            for k := range res {
                res[k].Filename = fp
            }
        }
        return nil
    })
    prepMsg(&res)
    return
}

func scanTmplString(s string) (res []spec.Msg) {
    t, err := prsTmpl.Parse("p", s, lDelim, rDelim, map[string]interface{}{})
    if err != nil { panic(err) }
    for _, va := range t {
        res = append(res, scanNodes(va.List.Nodes)...)
    }
    return
}

func scanNodes(nodes []prsTmpl.Node) (res []spec.Msg) {
    for _, node := range nodes {
        nt := node.Type().Type()
        switch nt {
        case prsTmpl.NodeAction:
            an := node.(*prsTmpl.ActionNode)
            res = append(res, scanActionNode(an)...)
        case prsTmpl.NodePipe:
            pn := node.(*prsTmpl.PipeNode)
            res = append(res, scanPipeNode(pn)...)
        case prsTmpl.NodeRange:
            rn := node.(*prsTmpl.RangeNode)
            res = append(res, scanNodes(rn.List.Nodes)...)
        case prsTmpl.NodeIf:
            in := node.(*prsTmpl.IfNode)
            res = append(res, scanNodes(in.List.Nodes)...)
            if in.ElseList != nil {
                res = append(res, scanNodes(in.ElseList.Nodes)...)
            }
        default:
            continue
        }
    }
    return
}

func scanActionNode(an *prsTmpl.ActionNode) []spec.Msg {
    pn := an.Pipe
    return scanPipeNode(pn)
}

func scanPipeNode(pn *prsTmpl.PipeNode) (res []spec.Msg) {
    cmds := pn.Cmds // PipeNode.[]CommandNode
    linePos := pn.Line  // line position
    for _, cmd := range cmds {
        ok, fun, msgs := false, "", []spec.Msg{}
        for k, arg := range cmd.Args {
            switch arg.Type() {
            case prsTmpl.NodeField, prsTmpl.NodeIdentifier, prsTmpl.NodeVariable: // func, method, var
                ok = true // allow for loop to roll through
                call := strings.Split(arg.String(), ".")
                switch call[len(call)-1] { // lock in for subsequent passes
                case gf:
                    fun = "G"   // "gettext"
                case ngf:
                    fun = "NG"  // "ngettext"
                case pgf:
                    fun = "PG"  // "ngettext"
                case npgf:
                    fun = "NPG" // "npgettext"
                default:
                    fun = ""
                }
            case prsTmpl.NodeString:
                ok = true
            }
            if !ok { continue }
            switch arg.Type() {
            case prsTmpl.NodePipe:
                msgs = append(msgs, scanPipeNode(arg.(*prsTmpl.PipeNode))...)
            case prsTmpl.NodeString:
                switch fun {
                case "G":
                    msgs = append(msgs, spec.Msg{Line: linePos, Id: arg.String()})
                case "NG":
                    switch k { // 1 - singular, 2 - plural, subsequent ignored
                    case 1:
                        msgs = append(msgs, spec.Msg{Line: linePos, Id: arg.String()})
                    case 2:
                        msgs[0].IdPlural = arg.String()
                    default:
                        msgs = append(msgs, spec.Msg{Line: linePos, Id: arg.String()})
                    }
                case "PG":
                    switch k { // 1 - context, 2 - text, subsequent ignored
                    case 1:
                        msgs = append(msgs, spec.Msg{Line: linePos, Ctxt: arg.String()})
                    case 2:
                        msgs[0].Id = arg.String()
                    default:
                        msgs = append(msgs, spec.Msg{Line: linePos, Id: arg.String()})
                    }
                case "NPG":
                    switch k { // 1 - context, 2 - singular, 3 - plural, subsequent ignored
                    case 1:
                        msgs = append(msgs, spec.Msg{Line: linePos, Ctxt: arg.String()})
                    case 2:
                        msgs[0].Id = arg.String()
                    case 3:
                        msgs[0].IdPlural = arg.String()
                    default:
                        msgs = append(msgs, spec.Msg{Line: linePos, Id: arg.String()})
                    }
                }
            }
        }
        res = append(res, msgs...)
    }
    return
}

func prepMsg(msgs *[]spec.Msg) {
    ps := string(os.PathSeparator)
    for k, v := range *msgs {
        // prep meta
        (*msgs)[k].Comments = make(spec.CommentPack)
        ref := strings.Join(strings.Split(v.Filename, o.General.DirProject+ps), "")
        ref += ":"+strconv.Itoa(v.Line)
        (*msgs)[k].Comments["reference"] = append(v.Comments["reference"], ref)

        // escape tilde-`quoted` strings (note that v is assigned to)
        switch {
        case len(v.Ctxt) > 0 && v.Ctxt[:1] == "`":
            v.Ctxt = escapeString(v.Ctxt)
        case len(v.Id) > 0 && v.Id[:1] == "`":
            v.Id = escapeString(v.Id)
        case len(v.IdPlural) > 0 && v.IdPlural[:1] == "`":
            v.IdPlural = escapeString(v.IdPlural)
        }

        // remove quotes
        if len(v.Ctxt) > 0 { (*msgs)[k].Ctxt = v.Ctxt[1:len(v.Ctxt)-1] }
        if len(v.Id) > 0 { (*msgs)[k].Id = v.Id[1:len(v.Id)-1] }
        if len(v.IdPlural) > 0 { (*msgs)[k].IdPlural = v.IdPlural[1:len(v.IdPlural)-1] }
    }
}

func escapeString(s string) string {
    r := strings.NewReplacer(`\`, `\\`, `"`, `\"`, "\t", `\t`, "\r", `\r`, "\n", `\n`)
    return r.Replace(s)
}<commit_msg>bugfix: filenames for templates are now recorded correctly<commit_after>package po

import (
	"fmt"
	prsTmpl "github.com/Sam-Izdat/pogo/deps/template/parse"
	spec "github.com/Sam-Izdat/pogo/gtspec"
	"go/ast"
	prsGo "go/parser"
	"go/token"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

var gf, ngf, pgf, npgf, lDelim, rDelim string
var cfgFN = "POGO.toml"

func init() {
	gf = o.Parsing.FuncG     // "gettext" function
	ngf = o.Parsing.FuncNG   // "ngettext" function
	pgf = o.Parsing.FuncPG   // "pgettext" function
	npgf = o.Parsing.FuncNPG // "npgettext" function
	lDelim = o.Parsing.DelimL
	rDelim = o.Parsing.DelimR
}

func ScanGo(path string) (res []spec.Msg) {
	filepath.Walk(path, func(fp string, fi os.FileInfo, err error) error {
		if err != nil {
			return nil
		}
		if !!fi.IsDir() {
			switch fi.Name() {
			case ".", "..":
				return nil
			default:
				tmp := scanGoDir(fp)
				if tmp != nil {
					res = append(res, tmp...)
				}
			}
		}
		return nil
	})
	prepMsg(&res)
	return
}

func scanGoDir(path string) (res []spec.Msg) {
	// skip any subdirectory with its own POGO.toml config
	conf := filepath.Join(path, cfgFN)
	if _, err := os.Stat(conf); err == nil && path != o.General.DirProject {
		return nil
	}

	fset := token.NewFileSet()
	pkgs, err := prsGo.ParseDir(fset, path, nil, 0)
	if err != nil {
		panic(err)
	}
	for _, pkg := range pkgs {
		for fn, f := range pkg.Files {
			ast.Inspect(f, func(n ast.Node) bool {
				var funcName string
				switch x := n.(type) {
				case *ast.CallExpr:
					switch y := x.Fun.(type) {
					case *ast.Ident: // function call
						funcName = y.Name
					case *ast.SelectorExpr: // method call
						funcName = y.Sel.Name
					}
					switch funcName {
					case gf, ngf, pgf, npgf: // do nothing
					default:
						return true
					}
					msgs := []spec.Msg{}
					for k, arg := range x.Args {
						switch y := arg.(type) {
						case *ast.BasicLit:
							linePos := fset.Position(y.ValuePos).Line
							switch funcName {
							case gf: // just singular
								msgs = append(msgs, spec.Msg{Filename: fn, Line: linePos, Id: y.Value})
							case ngf: // singular and plural
								switch k { // 0 - singular, 1 - plural, subsequent ignored
								case 0:
									msgs = append(msgs, spec.Msg{Filename: fn, Line: linePos, Id: y.Value})
								case 1:
									msgs[0].IdPlural = y.Value
								}
							case pgf: // context and text
								switch k { // 0 - context, 1 - text, subsequent ignored
								case 0:
									msgs = append(msgs, spec.Msg{Filename: fn, Line: linePos, Ctxt: y.Value})
								case 1:
									msgs[0].Id = y.Value
								}
							case npgf:
								switch k { // 0 - context, 1 - singular, 2 - plural, subsequent ignored
								case 0:
									msgs = append(msgs, spec.Msg{Filename: fn, Line: linePos, Ctxt: y.Value})
								case 1:
									msgs[0].Id = y.Value
								case 2:
									msgs[0].IdPlural = y.Value
								}
							}
						}
					}
					if len(msgs) > 0 {
						res = append(res, msgs...)
					}
				}
				return true
			})
		}
	}
	return
}

func ScanTmpl(path string) (res []spec.Msg) {
	filepath.Walk(path, func(fp string, fi os.FileInfo, err error) error {
		if err != nil {
			return nil
		}

		// skip any subdirectory with its own POGO.toml config
		dir := filepath.Base(fp)
		conf := filepath.Join(dir, cfgFN)
		if _, err := os.Stat(conf); err == nil && dir != o.General.DirProject {
			return nil
		}

		if !!fi.IsDir() {
			return nil
		}

		matched := false
		for _, ext := range o.Parsing.TmplExts {
			var err error
			matched, err = filepath.Match("*."+ext, fi.Name())
			if err != nil {
				panic(err)
			}
			if matched {
				break
			}
		}

		if matched {
			buf, err := ioutil.ReadFile(fp)
			if err != nil {
				panic(err)
			}
			tmpl := string(buf)
			scn := scanTmplString(tmpl)
			for k := range scn {
				scn[k].Filename = fp
			}
			res = append(res, scn...)
		}
		return nil
	})
	prepMsg(&res)
	return
}

func scanTmplString(s string) (res []spec.Msg) {
	t, err := prsTmpl.Parse("p", s, lDelim, rDelim, map[string]interface{}{})
	if err != nil {
		panic(err)
	}
	for _, va := range t {
		res = append(res, scanNodes(va.List.Nodes)...)
	}
	return
}

func scanNodes(nodes []prsTmpl.Node) (res []spec.Msg) {
	for _, node := range nodes {
		nt := node.Type().Type()
		switch nt {
		case prsTmpl.NodeAction:
			an := node.(*prsTmpl.ActionNode)
			res = append(res, scanActionNode(an)...)
		case prsTmpl.NodePipe:
			pn := node.(*prsTmpl.PipeNode)
			res = append(res, scanPipeNode(pn)...)
		case prsTmpl.NodeRange:
			rn := node.(*prsTmpl.RangeNode)
			res = append(res, scanNodes(rn.List.Nodes)...)
		case prsTmpl.NodeIf:
			in := node.(*prsTmpl.IfNode)
			res = append(res, scanNodes(in.List.Nodes)...)
			if in.ElseList != nil {
				res = append(res, scanNodes(in.ElseList.Nodes)...)
			}
		default:
			continue
		}
	}
	return
}

func scanActionNode(an *prsTmpl.ActionNode) []spec.Msg {
	pn := an.Pipe
	return scanPipeNode(pn)
}

func scanPipeNode(pn *prsTmpl.PipeNode) (res []spec.Msg) {
	cmds := pn.Cmds    // PipeNode.[]CommandNode
	linePos := pn.Line // line position
	for _, cmd := range cmds {
		ok, fun, msgs := false, "", []spec.Msg{}
		for k, arg := range cmd.Args {
			switch arg.Type() {
			case prsTmpl.NodeField, prsTmpl.NodeIdentifier, prsTmpl.NodeVariable: // func, method, var
				ok = true // allow for loop to roll through
				call := strings.Split(arg.String(), ".")
				switch call[len(call)-1] { // lock in for subsequent passes
				case gf:
					fun = "G" // "gettext"
				case ngf:
					fun = "NG" // "ngettext"
				case pgf:
					fun = "PG" // "ngettext"
				case npgf:
					fun = "NPG" // "npgettext"
				default:
					fun = ""
				}
			case prsTmpl.NodeString:
				ok = true
			}
			if !ok {
				continue
			}
			switch arg.Type() {
			case prsTmpl.NodePipe:
				msgs = append(msgs, scanPipeNode(arg.(*prsTmpl.PipeNode))...)
			case prsTmpl.NodeString:
				switch fun {
				case "G":
					msgs = append(msgs, spec.Msg{Line: linePos, Id: arg.String()})
				case "NG":
					switch k { // 1 - singular, 2 - plural, subsequent ignored
					case 1:
						msgs = append(msgs, spec.Msg{Line: linePos, Id: arg.String()})
					case 2:
						msgs[0].IdPlural = arg.String()
					default:
						msgs = append(msgs, spec.Msg{Line: linePos, Id: arg.String()})
					}
				case "PG":
					switch k { // 1 - context, 2 - text, subsequent ignored
					case 1:
						msgs = append(msgs, spec.Msg{Line: linePos, Ctxt: arg.String()})
					case 2:
						msgs[0].Id = arg.String()
					default:
						msgs = append(msgs, spec.Msg{Line: linePos, Id: arg.String()})
					}
				case "NPG":
					switch k { // 1 - context, 2 - singular, 3 - plural, subsequent ignored
					case 1:
						msgs = append(msgs, spec.Msg{Line: linePos, Ctxt: arg.String()})
					case 2:
						msgs[0].Id = arg.String()
					case 3:
						msgs[0].IdPlural = arg.String()
					default:
						msgs = append(msgs, spec.Msg{Line: linePos, Id: arg.String()})
					}
				}
			}
		}
		res = append(res, msgs...)
	}
	return
}

func prepMsg(msgs *[]spec.Msg) {
	ps := string(os.PathSeparator)
	for k, v := range *msgs {
		// prep meta
		(*msgs)[k].Comments = make(spec.CommentPack)
		ref := strings.Join(strings.Split(v.Filename, o.General.DirProject+ps), "")
		ref += ":" + strconv.Itoa(v.Line)
		(*msgs)[k].Comments["reference"] = append(v.Comments["reference"], ref)

		// escape tilde-`quoted` strings (note that v is assigned to)
		switch {
		case len(v.Ctxt) > 0 && v.Ctxt[:1] == "`":
			v.Ctxt = escapeString(v.Ctxt)
		case len(v.Id) > 0 && v.Id[:1] == "`":
			v.Id = escapeString(v.Id)
		case len(v.IdPlural) > 0 && v.IdPlural[:1] == "`":
			v.IdPlural = escapeString(v.IdPlural)
		}

		// remove quotes
		if len(v.Ctxt) > 0 {
			(*msgs)[k].Ctxt = v.Ctxt[1 : len(v.Ctxt)-1]
		}
		if len(v.Id) > 0 {
			(*msgs)[k].Id = v.Id[1 : len(v.Id)-1]
		}
		if len(v.IdPlural) > 0 {
			(*msgs)[k].IdPlural = v.IdPlural[1 : len(v.IdPlural)-1]
		}
	}
}

func escapeString(s string) string {
	r := strings.NewReplacer(`\`, `\\`, `"`, `\"`, "\t", `\t`, "\r", `\r`, "\n", `\n`)
	return r.Replace(s)
}
<|endoftext|>


<commit_before>package service

import (
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"runtime"
	"syscall"
	"time"
)

const (
	// Service defaults.
	DefaultStartTimeout = 1 * time.Second
	DefaultStartRetries = 3
	DefaultStopSignal   = syscall.SIGINT
	DefaultStopTimeout  = 5 * time.Second
	DefaultStopRestart  = true

	// Service commands.
	Start    = "start"
	Stop     = "stop"
	Restart  = "restart"
	Shutdown = "shutdown"

	// Service states.
	Starting = "starting"
	Running  = "running"
	Stopping = "stopping"
	Stopped  = "stopped"
	Exited   = "exited"
	Backoff  = "backoff"
	Fatal    = "fatal"
)

// Command is sent to a Service to initiate a state change.
type Command struct {
	Name     string
	Response chan<- Response
}

// respond creates and sends a command Response.
func (cmd Command) respond(service *Service, err error) {
	if cmd.Response != nil {
		cmd.Response <- Response{service, cmd.Name, err}
	}
}

// Response contains the result of a Command.
type Response struct {
	Service *Service
	Name    string
	Error   error
}

// Success returns True if the Command was successful.
func (r Response) Success() bool {
	return r.Error == nil
}

// Event is sent by a Service on a state change.
type Event struct {
	Service *Service // The service from which the event originated.
	State   string   // The new state of the service.
	Error   error    // An error indicating why the service is in Exited or Backoff.
}

// ExitError indicated why the service entered an Exited or Backoff state.
type ExitError string

// Error returns the error message of the ExitError.
func (err ExitError) Error() string {
	return string(err)
}

// Service represents a controllable process. Exported fields may be set to configure the service.
type Service struct {
	EventHook    func(*Service, string) // Function to call before an event is sent.
	Directory    string         // The process's working directory. Defaults to the current directory.
	Environment  []string       // The environment of the process. Defaults to nil which indicatesA the current environment.
	StartTimeout time.Duration  // How long the process has to run before it's considered Running.
	StartRetries int            // How many times to restart a process if it fails to start. Defaults to 3.
	StopSignal   syscall.Signal // The signal to send when stopping the process. Defaults to SIGINT.
	StopTimeout  time.Duration  // How long to wait for a process to stop before sending a SIGKILL. Defaults to 5s.
	StopRestart  bool           // Whether or not to restart the process if it exits unexpectedly. Defaults to true.
	Stdout       io.Writer      // Where to send the process's stdout. Defaults to /dev/null.
	Stderr       io.Writer      // Where to send the process's stderr. Defaults to /dev/null.
	args         []string       // The command line of the process to run.
	command      *exec.Cmd      // The os/exec command running the process.
	state        string         // The state of the Service.
}

// New creates a new service with the default configution.
func NewService(args []string) (svc *Service, err error) {
	if cwd, err := os.Getwd(); err == nil {
		svc = &Service{
			cwd,
			nil,
			DefaultStartTimeout,
			DefaultStartRetries,
			DefaultStopSignal,
			DefaultStopTimeout,
			DefaultStopRestart,
			nil,
			nil,
			nil,
			args,
			nil,
			Stopped,
		}
	}
	return
}

// State gets the current state of the service.
func (s Service) State() string {
	return s.state
}

// Pid gets the PID of the service or 0 if not Running or Stopping.
func (s Service) Pid() int {
	if s.state != Running && s.state != Stopping {
		return 0
	}
	return s.command.Process.Pid
}

func (s Service) makeCommand() *exec.Cmd {
	cmd := exec.Command(s.args[0], s.args[1:]...)
	cmd.Stdout = s.Stdout
	cmd.Stderr = s.Stderr
	cmd.Stdin = nil
	cmd.Env = s.Environment
	cmd.Dir = s.Directory
	return cmd
}

func (s *Service) Run(commands <-chan Command, events chan<- Event) {
	type ProcessState struct {
		State string
		Error error
	}

	var command *Command = nil
	states := make(chan ProcessState)
	kill := make(chan int, 2)
	retries := 0

	defer func() {
		close(states)
		close(kill)
	}()

	sendResponse := func(err error) {
		if command != nil {
			if command.Response != nil {
				command.respond(s, err)
			}
			command = nil
		}
	}

	sendEvent := func(state string, err error) {
		if s.EventHook != nil {
			s.EventHook(s, state)
		}
		s.state = state
		events <- Event{s, state, err}

		if command == nil {
			return
		}

		switch command.Name {
		case Restart:
			fallthrough
		case Start:
			if state == Running {
				sendResponse(nil)
			} else if state == Exited || state == Fatal {
				sendResponse(err)
			}
		case Stop:
			if state == Stopped {
				sendResponse(nil)
			} else if state == Exited {
				sendResponse(err)
			}
		}
	}

	invalidStateError := func(state string) error {
		return errors.New(fmt.Sprintf("invalid state transition: %s -> %s", s.state, state))
	}

	start := func() {
		if s.state != Stopped && s.state != Exited && s.state != Backoff && s.state != Fatal {
			sendResponse(invalidStateError(Starting))
			return
		}

		sendEvent(Starting, nil)
		go func() {
			s.command = s.makeCommand()
			if err := s.command.Start(); err == nil {
				waitOver := make(chan bool, 1)
				checkOver := make(chan bool, 1)

				defer func() {
					close(waitOver)
					close(checkOver)
				}()

				go func() {
					time.Sleep(s.StartTimeout)
					select {
					case <-waitOver:
						checkOver <-false
					default:
						states <- ProcessState{Running, nil}
						checkOver <-true
					}
				}()

				exitErr := s.command.Wait()
				waitOver <-true

				msg := ""
				if check := <-checkOver; check {
					if exitErr == nil {
						msg = "process exited normally with success"
					} else {
						msg = fmt.Sprintf("process exited normally with failure: %s", exitErr)
					}
					states <- ProcessState{Exited, ExitError(msg)}
				} else {
					if exitErr == nil {
						msg = "process exited prematurely with success"
					} else {
						msg = fmt.Sprintf("process exited prematurely with failure: %s", exitErr)
					}
					states <- ProcessState{Backoff, ExitError(msg)}
				}
			} else {
				states <- ProcessState{Exited, err}
			}
		}()
	}

	stop := func() {
		if s.state != Running {
			sendResponse(invalidStateError(Stopping))
			return
		}

		sendEvent(Stopping, nil)
		pid := s.Pid()
		s.command.Process.Signal(s.StopSignal) //TODO: Check for error.
		go func() {
			time.Sleep(s.StopTimeout)
			defer func() {
				if err := recover(); err != nil {
					if _, ok := err.(runtime.Error); !ok {
						panic(err)
					}
				}
			}()
			kill <- pid
		}()
	}

	shouldShutdown := func() bool {
		return command != nil && command.Name == Shutdown
	}

	shouldQuit := func() bool {
		return shouldShutdown() && (s.state == Stopped || s.state == Exited || s.state == Fatal)
	}

	for !shouldQuit() {
		select {
		case state := <-states:
			switch state.State {
			case Running:
				retries = 0
				if shouldShutdown() {
					stop()
				} else {
					sendEvent(Running, nil)
				}
			case Exited:
				retries = 0
				if s.state == Stopping {
					sendEvent(Stopped, nil)
				} else {
					sendEvent(Exited, state.Error)
					if s.StopRestart {
						start()
					}
				}
			case Backoff:
				if s.state == Stopping {
					retries = 0
					sendEvent(Stopped, nil)
				} else {
					if retries < s.StartRetries {
						retries++
						sendEvent(Backoff, state.Error)
						start()
					} else {
						retries = 0
						sendEvent(Fatal, state.Error)
					}
				}
			}
		case newCommand := <-commands:
			if command != nil {
				if newCommand.Name == Shutdown {
					// Fail previous command to force shutdown.
					command.respond(s, errors.New("service is shuttind down"))
				} else {
					// Don't allow execution of more than one command at a time.
					newCommand.respond(s, errors.New("command %s is currently executing"))
					continue
				}
			}

			command = &newCommand
			switch command.Name {
			case Start:
				start()
			case Stop:
				stop()
			case Restart:
				switch s.state {
				case Running:
					stop()
				case Stopped:
					start()
				case Exited:
					start()
				case Fatal:
					start()
				default:
					sendResponse(invalidStateError(Stopping))
				}
			case Shutdown:
				switch s.state {
				case Running:
					stop()
				case Backoff:
					s.state = Fatal
				}
			}
		case pid := <-kill:
			if pid == s.Pid() {
				s.command.Process.Kill() //TODO: Check for error.
			}
		}
	}

	if command != nil {
		command.respond(s, nil)
	}
}
<commit_msg>gofmt<commit_after>package service

import (
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"runtime"
	"syscall"
	"time"
)

const (
	// Service defaults.
	DefaultStartTimeout = 1 * time.Second
	DefaultStartRetries = 3
	DefaultStopSignal   = syscall.SIGINT
	DefaultStopTimeout  = 5 * time.Second
	DefaultStopRestart  = true

	// Service commands.
	Start    = "start"
	Stop     = "stop"
	Restart  = "restart"
	Shutdown = "shutdown"

	// Service states.
	Starting = "starting"
	Running  = "running"
	Stopping = "stopping"
	Stopped  = "stopped"
	Exited   = "exited"
	Backoff  = "backoff"
	Fatal    = "fatal"
)

// Command is sent to a Service to initiate a state change.
type Command struct {
	Name     string
	Response chan<- Response
}

// respond creates and sends a command Response.
func (cmd Command) respond(service *Service, err error) {
	if cmd.Response != nil {
		cmd.Response <- Response{service, cmd.Name, err}
	}
}

// Response contains the result of a Command.
type Response struct {
	Service *Service
	Name    string
	Error   error
}

// Success returns True if the Command was successful.
func (r Response) Success() bool {
	return r.Error == nil
}

// Event is sent by a Service on a state change.
type Event struct {
	Service *Service // The service from which the event originated.
	State   string   // The new state of the service.
	Error   error    // An error indicating why the service is in Exited or Backoff.
}

// ExitError indicated why the service entered an Exited or Backoff state.
type ExitError string

// Error returns the error message of the ExitError.
func (err ExitError) Error() string {
	return string(err)
}

// Service represents a controllable process. Exported fields may be set to configure the service.
type Service struct {
	Directory    string                 // The process's working directory. Defaults to the current directory.
	Environment  []string               // The environment of the process. Defaults to nil which indicatesA the current environment.
	StartTimeout time.Duration          // How long the process has to run before it's considered Running.
	StartRetries int                    // How many times to restart a process if it fails to start. Defaults to 3.
	StopSignal   syscall.Signal         // The signal to send when stopping the process. Defaults to SIGINT.
	StopTimeout  time.Duration          // How long to wait for a process to stop before sending a SIGKILL. Defaults to 5s.
	StopRestart  bool                   // Whether or not to restart the process if it exits unexpectedly. Defaults to true.
	Stdout       io.Writer              // Where to send the process's stdout. Defaults to /dev/null.
	Stderr       io.Writer              // Where to send the process's stderr. Defaults to /dev/null.
	EventHook    func(*Service, string) // Function to call before an event is sent.
	args         []string               // The command line of the process to run.
	command      *exec.Cmd              // The os/exec command running the process.
	state        string                 // The state of the Service.
}

// New creates a new service with the default configution.
func NewService(args []string) (svc *Service, err error) {
	if cwd, err := os.Getwd(); err == nil {
		svc = &Service{
			cwd,
			nil,
			DefaultStartTimeout,
			DefaultStartRetries,
			DefaultStopSignal,
			DefaultStopTimeout,
			DefaultStopRestart,
			nil,
			nil,
			nil,
			args,
			nil,
			Stopped,
		}
	}
	return
}

// State gets the current state of the service.
func (s Service) State() string {
	return s.state
}

// Pid gets the PID of the service or 0 if not Running or Stopping.
func (s Service) Pid() int {
	if s.state != Running && s.state != Stopping {
		return 0
	}
	return s.command.Process.Pid
}

func (s Service) makeCommand() *exec.Cmd {
	cmd := exec.Command(s.args[0], s.args[1:]...)
	cmd.Stdout = s.Stdout
	cmd.Stderr = s.Stderr
	cmd.Stdin = nil
	cmd.Env = s.Environment
	cmd.Dir = s.Directory
	return cmd
}

func (s *Service) Run(commands <-chan Command, events chan<- Event) {
	type ProcessState struct {
		State string
		Error error
	}

	var command *Command = nil
	states := make(chan ProcessState)
	kill := make(chan int, 2)
	retries := 0

	defer func() {
		close(states)
		close(kill)
	}()

	sendResponse := func(err error) {
		if command != nil {
			if command.Response != nil {
				command.respond(s, err)
			}
			command = nil
		}
	}

	sendEvent := func(state string, err error) {
		if s.EventHook != nil {
			s.EventHook(s, state)
		}
		s.state = state
		events <- Event{s, state, err}

		if command == nil {
			return
		}

		switch command.Name {
		case Restart:
			fallthrough
		case Start:
			if state == Running {
				sendResponse(nil)
			} else if state == Exited || state == Fatal {
				sendResponse(err)
			}
		case Stop:
			if state == Stopped {
				sendResponse(nil)
			} else if state == Exited {
				sendResponse(err)
			}
		}
	}

	invalidStateError := func(state string) error {
		return errors.New(fmt.Sprintf("invalid state transition: %s -> %s", s.state, state))
	}

	start := func() {
		if s.state != Stopped && s.state != Exited && s.state != Backoff && s.state != Fatal {
			sendResponse(invalidStateError(Starting))
			return
		}

		sendEvent(Starting, nil)
		go func() {
			s.command = s.makeCommand()
			if err := s.command.Start(); err == nil {
				waitOver := make(chan bool, 1)
				checkOver := make(chan bool, 1)

				defer func() {
					close(waitOver)
					close(checkOver)
				}()

				go func() {
					time.Sleep(s.StartTimeout)
					select {
					case <-waitOver:
						checkOver <- false
					default:
						states <- ProcessState{Running, nil}
						checkOver <- true
					}
				}()

				exitErr := s.command.Wait()
				waitOver <- true

				msg := ""
				if check := <-checkOver; check {
					if exitErr == nil {
						msg = "process exited normally with success"
					} else {
						msg = fmt.Sprintf("process exited normally with failure: %s", exitErr)
					}
					states <- ProcessState{Exited, ExitError(msg)}
				} else {
					if exitErr == nil {
						msg = "process exited prematurely with success"
					} else {
						msg = fmt.Sprintf("process exited prematurely with failure: %s", exitErr)
					}
					states <- ProcessState{Backoff, ExitError(msg)}
				}
			} else {
				states <- ProcessState{Exited, err}
			}
		}()
	}

	stop := func() {
		if s.state != Running {
			sendResponse(invalidStateError(Stopping))
			return
		}

		sendEvent(Stopping, nil)
		pid := s.Pid()
		s.command.Process.Signal(s.StopSignal) //TODO: Check for error.
		go func() {
			time.Sleep(s.StopTimeout)
			defer func() {
				if err := recover(); err != nil {
					if _, ok := err.(runtime.Error); !ok {
						panic(err)
					}
				}
			}()
			kill <- pid
		}()
	}

	shouldShutdown := func() bool {
		return command != nil && command.Name == Shutdown
	}

	shouldQuit := func() bool {
		return shouldShutdown() && (s.state == Stopped || s.state == Exited || s.state == Fatal)
	}

	for !shouldQuit() {
		select {
		case state := <-states:
			switch state.State {
			case Running:
				retries = 0
				if shouldShutdown() {
					stop()
				} else {
					sendEvent(Running, nil)
				}
			case Exited:
				retries = 0
				if s.state == Stopping {
					sendEvent(Stopped, nil)
				} else {
					sendEvent(Exited, state.Error)
					if s.StopRestart {
						start()
					}
				}
			case Backoff:
				if s.state == Stopping {
					retries = 0
					sendEvent(Stopped, nil)
				} else {
					if retries < s.StartRetries {
						retries++
						sendEvent(Backoff, state.Error)
						start()
					} else {
						retries = 0
						sendEvent(Fatal, state.Error)
					}
				}
			}
		case newCommand := <-commands:
			if command != nil {
				if newCommand.Name == Shutdown {
					// Fail previous command to force shutdown.
					command.respond(s, errors.New("service is shuttind down"))
				} else {
					// Don't allow execution of more than one command at a time.
					newCommand.respond(s, errors.New("command %s is currently executing"))
					continue
				}
			}

			command = &newCommand
			switch command.Name {
			case Start:
				start()
			case Stop:
				stop()
			case Restart:
				switch s.state {
				case Running:
					stop()
				case Stopped:
					start()
				case Exited:
					start()
				case Fatal:
					start()
				default:
					sendResponse(invalidStateError(Stopping))
				}
			case Shutdown:
				switch s.state {
				case Running:
					stop()
				case Backoff:
					s.state = Fatal
				}
			}
		case pid := <-kill:
			if pid == s.Pid() {
				s.command.Process.Kill() //TODO: Check for error.
			}
		}
	}

	if command != nil {
		command.respond(s, nil)
	}
}
<|endoftext|>


<commit_before>package commands

import (
	"errors"
	"fmt"
	"os"
	"sort"
	"strings"

	"github.com/concourse/concourse/fly/commands/internal/displayhelpers"
	"github.com/concourse/concourse/fly/rc"
	"github.com/concourse/concourse/fly/ui"
	"github.com/fatih/color"
)

type GetTeamCommand struct {
	Team string `short:"n" long:"team" required:"true" description:"Get configuration of this team"`
	Json bool   `short:"j" long:"json" description:"Print config as json instead of yaml"`
}

func (command *GetTeamCommand) Execute(args []string) error {
	target, err := rc.LoadTarget(Fly.Target, Fly.Verbose)
	if err != nil {
		return err
	}

	if err := target.Validate(); err != nil {
		return err
	}

	team, found, err := target.Team().Team(command.Team)
	if err != nil {
		return err
	}

	if !found {
		return errors.New("team not found")
	}

	if command.Json {
		if err := displayhelpers.JsonPrint(team); err != nil {
			return err
		}
		return nil
	}

	headers := ui.TableRow{
		{Contents: "name/role", Color: color.New(color.Bold)},
		{Contents: "users", Color: color.New(color.Bold)},
		{Contents: "groups", Color: color.New(color.Bold)},
	}
	table := ui.Table{Headers: headers}
	for role, auth := range team.Auth {
		row := ui.TableRow{
			{Contents: fmt.Sprintf("%s/%s", team.Name, role)},
		}
		var usersCell, groupsCell ui.TableCell
		hasUsers := len(auth["users"]) != 0
		hasGroups := len(auth["groups"]) != 0

		if !hasUsers && !hasGroups {
			usersCell.Contents = "all"
			usersCell.Color = color.New(color.Faint)
		} else if !hasUsers {
			usersCell.Contents = "none"
			usersCell.Color = color.New(color.Faint)
		} else {
			usersCell.Contents = strings.Join(auth["users"], ",")
		}

		if hasGroups {
			groupsCell.Contents = strings.Join(auth["groups"], ",")
		} else {
			groupsCell.Contents = "none"
			groupsCell.Color = color.New(color.Faint)
		}

		row = append(row, usersCell)
		row = append(row, groupsCell)
		table.Data = append(table.Data, row)
	}
	sort.Sort(table.Data)
	return table.Render(os.Stdout, Fly.PrintTableHeaders)
}
<commit_msg>fix #4045<commit_after>package commands

import (
	"errors"
	"fmt"
	"os"
	"sort"
	"strings"

	"github.com/concourse/concourse/fly/commands/internal/displayhelpers"
	"github.com/concourse/concourse/fly/rc"
	"github.com/concourse/concourse/fly/ui"
	"github.com/fatih/color"
)

type GetTeamCommand struct {
	Team string `short:"n" long:"team" required:"true" description:"Get configuration of this team"`
	JSON bool   `short:"j" long:"json" description:"Print command result as JSON"`
}

func (command *GetTeamCommand) Execute(args []string) error {
	target, err := rc.LoadTarget(Fly.Target, Fly.Verbose)
	if err != nil {
		return err
	}

	if err := target.Validate(); err != nil {
		return err
	}

	team, found, err := target.Team().Team(command.Team)
	if err != nil {
		return err
	}

	if !found {
		return errors.New("team not found")
	}

	if command.JSON {
		if err := displayhelpers.JsonPrint(team); err != nil {
			return err
		}
		return nil
	}

	headers := ui.TableRow{
		{Contents: "name/role", Color: color.New(color.Bold)},
		{Contents: "users", Color: color.New(color.Bold)},
		{Contents: "groups", Color: color.New(color.Bold)},
	}
	table := ui.Table{Headers: headers}
	for role, auth := range team.Auth {
		row := ui.TableRow{
			{Contents: fmt.Sprintf("%s/%s", team.Name, role)},
		}
		var usersCell, groupsCell ui.TableCell
		hasUsers := len(auth["users"]) != 0
		hasGroups := len(auth["groups"]) != 0

		if !hasUsers && !hasGroups {
			usersCell.Contents = "all"
			usersCell.Color = color.New(color.Faint)
		} else if !hasUsers {
			usersCell.Contents = "none"
			usersCell.Color = color.New(color.Faint)
		} else {
			usersCell.Contents = strings.Join(auth["users"], ",")
		}

		if hasGroups {
			groupsCell.Contents = strings.Join(auth["groups"], ",")
		} else {
			groupsCell.Contents = "none"
			groupsCell.Color = color.New(color.Faint)
		}

		row = append(row, usersCell)
		row = append(row, groupsCell)
		table.Data = append(table.Data, row)
	}
	sort.Sort(table.Data)
	return table.Render(os.Stdout, Fly.PrintTableHeaders)
}
<|endoftext|>


<commit_before>package snapcraft

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"

	"github.com/goreleaser/goreleaser/config"
	"github.com/goreleaser/goreleaser/context"
	"github.com/stretchr/testify/assert"
)

func TestDescription(t *testing.T) {
	assert.NotEmpty(t, Pipe{}.Description())
}

func TestRunPipeMissingInfo(t *testing.T) {
	for name, snap := range map[string]config.Snapcraft{
		"missing summary": {
			Description: "dummy desc",
		},
		"missing description": {
			Summary: "dummy summary",
		},
	} {
		t.Run(name, func(t *testing.T) {
			var assert = assert.New(t)
			var ctx = &context.Context{
				Config: config.Project{
					Snapcraft: snap,
				},
			}
			assert.NoError(Pipe{}.Run(ctx))
		})
	}
}

func TestRunPipe(t *testing.T) {
	var assert = assert.New(t)
	folder, err := ioutil.TempDir("", "archivetest")
	assert.NoError(err)
	var dist = filepath.Join(folder, "dist")
	assert.NoError(os.Mkdir(dist, 0755))
	assert.NoError(os.Mkdir(filepath.Join(dist, "mybin"), 0755))
	var binPath = filepath.Join(dist, "mybin", "mybin")
	_, err = os.Create(binPath)
	assert.NoError(err)
	var ctx = &context.Context{
		Version: "testversion",
		Config: config.Project{
			ProjectName: "mybin",
			Dist:        dist,
			Snapcraft: config.Snapcraft{
				Summary:     "test summary",
				Description: "test description",
			},
		},
	}
	for _, plat := range []string{"linuxamd64", "linux386", "darwinamd64"} {
		ctx.AddBinary(plat, "mybin", "mybin", binPath)
	}
	assert.NoError(Pipe{}.Run(ctx))
}

func TestNoSnapcraftInPath(t *testing.T) {
	var assert = assert.New(t)
	var path = os.Getenv("PATH")
	defer func() {
		assert.NoError(os.Setenv("PATH", path))
	}()
	assert.NoError(os.Setenv("PATH", ""))
	var ctx = &context.Context{
		Config: config.Project{
			Snapcraft: config.Snapcraft{
				Summary:     "dummy",
				Description: "dummy",
			},
		},
	}
	assert.EqualError(Pipe{}.Run(ctx), ErrNoSnapcraft.Error())
}
<commit_msg>maybe fixing test<commit_after>package snapcraft

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"

	"github.com/goreleaser/goreleaser/config"
	"github.com/goreleaser/goreleaser/context"
	"github.com/stretchr/testify/assert"
)

func TestDescription(t *testing.T) {
	assert.NotEmpty(t, Pipe{}.Description())
}

func TestRunPipeMissingInfo(t *testing.T) {
	for name, snap := range map[string]config.Snapcraft{
		"missing summary": {
			Description: "dummy desc",
		},
		"missing description": {
			Summary: "dummy summary",
		},
	} {
		t.Run(name, func(t *testing.T) {
			var assert = assert.New(t)
			var ctx = &context.Context{
				Config: config.Project{
					Snapcraft: snap,
				},
			}
			assert.NoError(Pipe{}.Run(ctx))
		})
	}
}

func TestRunPipe(t *testing.T) {
	var assert = assert.New(t)
	folder, err := ioutil.TempDir("", "archivetest")
	assert.NoError(err)
	var dist = filepath.Join(folder, "dist")
	assert.NoError(os.Mkdir(dist, 0755))
	assert.NoError(err)
	var ctx = &context.Context{
		Version: "testversion",
		Config: config.Project{
			ProjectName: "mybin",
			Dist:        dist,
			Snapcraft: config.Snapcraft{
				Summary:     "test summary",
				Description: "test description",
			},
		},
	}
	for _, plat := range []string{"linuxamd64", "linux386", "darwinamd64"} {
		var folder = "mybin_" + plat
		assert.NoError(os.Mkdir(filepath.Join(dist, folder), 0755))
		var binPath = filepath.Join(dist, folder, "mybin")
		_, err = os.Create(binPath)
		ctx.AddBinary(plat, folder, "mybin", binPath)
	}
	assert.NoError(Pipe{}.Run(ctx))
}

func TestNoSnapcraftInPath(t *testing.T) {
	var assert = assert.New(t)
	var path = os.Getenv("PATH")
	defer func() {
		assert.NoError(os.Setenv("PATH", path))
	}()
	assert.NoError(os.Setenv("PATH", ""))
	var ctx = &context.Context{
		Config: config.Project{
			Snapcraft: config.Snapcraft{
				Summary:     "dummy",
				Description: "dummy",
			},
		},
	}
	assert.EqualError(Pipe{}.Run(ctx), ErrNoSnapcraft.Error())
}
<|endoftext|>


<commit_before>/*
Copyright 2014 The Kubernetes 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 options

// This file exists to force the desired plugin implementations to be linked.
// This should probably be part of some configuration fed into the build for a
// given binary target.
import (
	// Admission policies
	"k8s.io/kubernetes/plugin/pkg/admission/admit"
	"k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages"
	"k8s.io/kubernetes/plugin/pkg/admission/antiaffinity"
	certapproval "k8s.io/kubernetes/plugin/pkg/admission/certificates/approval"
	certsigning "k8s.io/kubernetes/plugin/pkg/admission/certificates/signing"
	certsubjectrestriction "k8s.io/kubernetes/plugin/pkg/admission/certificates/subjectrestriction"
	"k8s.io/kubernetes/plugin/pkg/admission/defaulttolerationseconds"
	"k8s.io/kubernetes/plugin/pkg/admission/deny"
	"k8s.io/kubernetes/plugin/pkg/admission/eventratelimit"
	"k8s.io/kubernetes/plugin/pkg/admission/extendedresourcetoleration"
	"k8s.io/kubernetes/plugin/pkg/admission/gc"
	"k8s.io/kubernetes/plugin/pkg/admission/imagepolicy"
	"k8s.io/kubernetes/plugin/pkg/admission/limitranger"
	"k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision"
	"k8s.io/kubernetes/plugin/pkg/admission/namespace/exists"
	"k8s.io/kubernetes/plugin/pkg/admission/network/defaultingressclass"
	"k8s.io/kubernetes/plugin/pkg/admission/network/denyserviceexternalips"
	"k8s.io/kubernetes/plugin/pkg/admission/noderestriction"
	"k8s.io/kubernetes/plugin/pkg/admission/nodetaint"
	"k8s.io/kubernetes/plugin/pkg/admission/podnodeselector"
	"k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction"
	podpriority "k8s.io/kubernetes/plugin/pkg/admission/priority"
	"k8s.io/kubernetes/plugin/pkg/admission/runtimeclass"
	"k8s.io/kubernetes/plugin/pkg/admission/security/podsecurity"
	"k8s.io/kubernetes/plugin/pkg/admission/security/podsecuritypolicy"
	"k8s.io/kubernetes/plugin/pkg/admission/securitycontext/scdeny"
	"k8s.io/kubernetes/plugin/pkg/admission/serviceaccount"
	"k8s.io/kubernetes/plugin/pkg/admission/storage/persistentvolume/label"
	"k8s.io/kubernetes/plugin/pkg/admission/storage/persistentvolume/resize"
	"k8s.io/kubernetes/plugin/pkg/admission/storage/storageclass/setdefault"
	"k8s.io/kubernetes/plugin/pkg/admission/storage/storageobjectinuseprotection"

	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apiserver/pkg/admission"
	"k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle"
	"k8s.io/apiserver/pkg/admission/plugin/resourcequota"
	mutatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/mutating"
	validatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/validating"
)

// AllOrderedPlugins is the list of all the plugins in order.
var AllOrderedPlugins = []string{
	admit.PluginName,                        // AlwaysAdmit
	autoprovision.PluginName,                // NamespaceAutoProvision
	lifecycle.PluginName,                    // NamespaceLifecycle
	exists.PluginName,                       // NamespaceExists
	scdeny.PluginName,                       // SecurityContextDeny
	antiaffinity.PluginName,                 // LimitPodHardAntiAffinityTopology
	limitranger.PluginName,                  // LimitRanger
	serviceaccount.PluginName,               // ServiceAccount
	noderestriction.PluginName,              // NodeRestriction
	nodetaint.PluginName,                    // TaintNodesByCondition
	alwayspullimages.PluginName,             // AlwaysPullImages
	imagepolicy.PluginName,                  // ImagePolicyWebhook
	podsecuritypolicy.PluginName,            // PodSecurityPolicy
	podsecurity.PluginName,                  // PodSecurity
	podnodeselector.PluginName,              // PodNodeSelector
	podpriority.PluginName,                  // Priority
	defaulttolerationseconds.PluginName,     // DefaultTolerationSeconds
	podtolerationrestriction.PluginName,     // PodTolerationRestriction
	eventratelimit.PluginName,               // EventRateLimit
	extendedresourcetoleration.PluginName,   // ExtendedResourceToleration
	label.PluginName,                        // PersistentVolumeLabel
	setdefault.PluginName,                   // DefaultStorageClass
	storageobjectinuseprotection.PluginName, // StorageObjectInUseProtection
	gc.PluginName,                           // OwnerReferencesPermissionEnforcement
	resize.PluginName,                       // PersistentVolumeClaimResize
	runtimeclass.PluginName,                 // RuntimeClass
	certapproval.PluginName,                 // CertificateApproval
	certsigning.PluginName,                  // CertificateSigning
	certsubjectrestriction.PluginName,       // CertificateSubjectRestriction
	defaultingressclass.PluginName,          // DefaultIngressClass
	denyserviceexternalips.PluginName,       // DenyServiceExternalIPs

	// new admission plugins should generally be inserted above here
	// webhook, resourcequota, and deny plugins must go at the end

	mutatingwebhook.PluginName,   // MutatingAdmissionWebhook
	validatingwebhook.PluginName, // ValidatingAdmissionWebhook
	resourcequota.PluginName,     // ResourceQuota
	deny.PluginName,              // AlwaysDeny
}

// RegisterAllAdmissionPlugins registers all admission plugins and
// sets the recommended plugins order.
func RegisterAllAdmissionPlugins(plugins *admission.Plugins) {
	admit.Register(plugins) // DEPRECATED as no real meaning
	alwayspullimages.Register(plugins)
	antiaffinity.Register(plugins)
	defaulttolerationseconds.Register(plugins)
	defaultingressclass.Register(plugins)
	denyserviceexternalips.Register(plugins)
	deny.Register(plugins) // DEPRECATED as no real meaning
	eventratelimit.Register(plugins)
	extendedresourcetoleration.Register(plugins)
	gc.Register(plugins)
	imagepolicy.Register(plugins)
	limitranger.Register(plugins)
	autoprovision.Register(plugins)
	exists.Register(plugins)
	noderestriction.Register(plugins)
	nodetaint.Register(plugins)
	label.Register(plugins) // DEPRECATED, future PVs should not rely on labels for zone topology
	podnodeselector.Register(plugins)
	podtolerationrestriction.Register(plugins)
	runtimeclass.Register(plugins)
	resourcequota.Register(plugins)
	podsecurity.Register(plugins) // before PodSecurityPolicy so audit/warn get exercised even if PodSecurityPolicy denies
	podsecuritypolicy.Register(plugins)
	podpriority.Register(plugins)
	scdeny.Register(plugins)
	serviceaccount.Register(plugins)
	setdefault.Register(plugins)
	resize.Register(plugins)
	storageobjectinuseprotection.Register(plugins)
	certapproval.Register(plugins)
	certsigning.Register(plugins)
	certsubjectrestriction.Register(plugins)
}

// DefaultOffAdmissionPlugins get admission plugins off by default for kube-apiserver.
func DefaultOffAdmissionPlugins() sets.String {
	defaultOnPlugins := sets.NewString(
		lifecycle.PluginName,                    // NamespaceLifecycle
		limitranger.PluginName,                  // LimitRanger
		serviceaccount.PluginName,               // ServiceAccount
		setdefault.PluginName,                   // DefaultStorageClass
		resize.PluginName,                       // PersistentVolumeClaimResize
		defaulttolerationseconds.PluginName,     // DefaultTolerationSeconds
		mutatingwebhook.PluginName,              // MutatingAdmissionWebhook
		validatingwebhook.PluginName,            // ValidatingAdmissionWebhook
		resourcequota.PluginName,                // ResourceQuota
		storageobjectinuseprotection.PluginName, // StorageObjectInUseProtection
		podpriority.PluginName,                  // PodPriority
		nodetaint.PluginName,                    // TaintNodesByCondition
		runtimeclass.PluginName,                 // RuntimeClass
		certapproval.PluginName,                 // CertificateApproval
		certsigning.PluginName,                  // CertificateSigning
		certsubjectrestriction.PluginName,       // CertificateSubjectRestriction
		defaultingressclass.PluginName,          // DefaultIngressClass
		podsecurity.PluginName,                  // PodSecurity
	)

	return sets.NewString(AllOrderedPlugins...).Difference(defaultOnPlugins)
}
<commit_msg>Fix Priority plugin comment<commit_after>/*
Copyright 2014 The Kubernetes 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 options

// This file exists to force the desired plugin implementations to be linked.
// This should probably be part of some configuration fed into the build for a
// given binary target.
import (
	// Admission policies
	"k8s.io/kubernetes/plugin/pkg/admission/admit"
	"k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages"
	"k8s.io/kubernetes/plugin/pkg/admission/antiaffinity"
	certapproval "k8s.io/kubernetes/plugin/pkg/admission/certificates/approval"
	certsigning "k8s.io/kubernetes/plugin/pkg/admission/certificates/signing"
	certsubjectrestriction "k8s.io/kubernetes/plugin/pkg/admission/certificates/subjectrestriction"
	"k8s.io/kubernetes/plugin/pkg/admission/defaulttolerationseconds"
	"k8s.io/kubernetes/plugin/pkg/admission/deny"
	"k8s.io/kubernetes/plugin/pkg/admission/eventratelimit"
	"k8s.io/kubernetes/plugin/pkg/admission/extendedresourcetoleration"
	"k8s.io/kubernetes/plugin/pkg/admission/gc"
	"k8s.io/kubernetes/plugin/pkg/admission/imagepolicy"
	"k8s.io/kubernetes/plugin/pkg/admission/limitranger"
	"k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision"
	"k8s.io/kubernetes/plugin/pkg/admission/namespace/exists"
	"k8s.io/kubernetes/plugin/pkg/admission/network/defaultingressclass"
	"k8s.io/kubernetes/plugin/pkg/admission/network/denyserviceexternalips"
	"k8s.io/kubernetes/plugin/pkg/admission/noderestriction"
	"k8s.io/kubernetes/plugin/pkg/admission/nodetaint"
	"k8s.io/kubernetes/plugin/pkg/admission/podnodeselector"
	"k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction"
	podpriority "k8s.io/kubernetes/plugin/pkg/admission/priority"
	"k8s.io/kubernetes/plugin/pkg/admission/runtimeclass"
	"k8s.io/kubernetes/plugin/pkg/admission/security/podsecurity"
	"k8s.io/kubernetes/plugin/pkg/admission/security/podsecuritypolicy"
	"k8s.io/kubernetes/plugin/pkg/admission/securitycontext/scdeny"
	"k8s.io/kubernetes/plugin/pkg/admission/serviceaccount"
	"k8s.io/kubernetes/plugin/pkg/admission/storage/persistentvolume/label"
	"k8s.io/kubernetes/plugin/pkg/admission/storage/persistentvolume/resize"
	"k8s.io/kubernetes/plugin/pkg/admission/storage/storageclass/setdefault"
	"k8s.io/kubernetes/plugin/pkg/admission/storage/storageobjectinuseprotection"

	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apiserver/pkg/admission"
	"k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle"
	"k8s.io/apiserver/pkg/admission/plugin/resourcequota"
	mutatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/mutating"
	validatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/validating"
)

// AllOrderedPlugins is the list of all the plugins in order.
var AllOrderedPlugins = []string{
	admit.PluginName,                        // AlwaysAdmit
	autoprovision.PluginName,                // NamespaceAutoProvision
	lifecycle.PluginName,                    // NamespaceLifecycle
	exists.PluginName,                       // NamespaceExists
	scdeny.PluginName,                       // SecurityContextDeny
	antiaffinity.PluginName,                 // LimitPodHardAntiAffinityTopology
	limitranger.PluginName,                  // LimitRanger
	serviceaccount.PluginName,               // ServiceAccount
	noderestriction.PluginName,              // NodeRestriction
	nodetaint.PluginName,                    // TaintNodesByCondition
	alwayspullimages.PluginName,             // AlwaysPullImages
	imagepolicy.PluginName,                  // ImagePolicyWebhook
	podsecuritypolicy.PluginName,            // PodSecurityPolicy
	podsecurity.PluginName,                  // PodSecurity
	podnodeselector.PluginName,              // PodNodeSelector
	podpriority.PluginName,                  // Priority
	defaulttolerationseconds.PluginName,     // DefaultTolerationSeconds
	podtolerationrestriction.PluginName,     // PodTolerationRestriction
	eventratelimit.PluginName,               // EventRateLimit
	extendedresourcetoleration.PluginName,   // ExtendedResourceToleration
	label.PluginName,                        // PersistentVolumeLabel
	setdefault.PluginName,                   // DefaultStorageClass
	storageobjectinuseprotection.PluginName, // StorageObjectInUseProtection
	gc.PluginName,                           // OwnerReferencesPermissionEnforcement
	resize.PluginName,                       // PersistentVolumeClaimResize
	runtimeclass.PluginName,                 // RuntimeClass
	certapproval.PluginName,                 // CertificateApproval
	certsigning.PluginName,                  // CertificateSigning
	certsubjectrestriction.PluginName,       // CertificateSubjectRestriction
	defaultingressclass.PluginName,          // DefaultIngressClass
	denyserviceexternalips.PluginName,       // DenyServiceExternalIPs

	// new admission plugins should generally be inserted above here
	// webhook, resourcequota, and deny plugins must go at the end

	mutatingwebhook.PluginName,   // MutatingAdmissionWebhook
	validatingwebhook.PluginName, // ValidatingAdmissionWebhook
	resourcequota.PluginName,     // ResourceQuota
	deny.PluginName,              // AlwaysDeny
}

// RegisterAllAdmissionPlugins registers all admission plugins and
// sets the recommended plugins order.
func RegisterAllAdmissionPlugins(plugins *admission.Plugins) {
	admit.Register(plugins) // DEPRECATED as no real meaning
	alwayspullimages.Register(plugins)
	antiaffinity.Register(plugins)
	defaulttolerationseconds.Register(plugins)
	defaultingressclass.Register(plugins)
	denyserviceexternalips.Register(plugins)
	deny.Register(plugins) // DEPRECATED as no real meaning
	eventratelimit.Register(plugins)
	extendedresourcetoleration.Register(plugins)
	gc.Register(plugins)
	imagepolicy.Register(plugins)
	limitranger.Register(plugins)
	autoprovision.Register(plugins)
	exists.Register(plugins)
	noderestriction.Register(plugins)
	nodetaint.Register(plugins)
	label.Register(plugins) // DEPRECATED, future PVs should not rely on labels for zone topology
	podnodeselector.Register(plugins)
	podtolerationrestriction.Register(plugins)
	runtimeclass.Register(plugins)
	resourcequota.Register(plugins)
	podsecurity.Register(plugins) // before PodSecurityPolicy so audit/warn get exercised even if PodSecurityPolicy denies
	podsecuritypolicy.Register(plugins)
	podpriority.Register(plugins)
	scdeny.Register(plugins)
	serviceaccount.Register(plugins)
	setdefault.Register(plugins)
	resize.Register(plugins)
	storageobjectinuseprotection.Register(plugins)
	certapproval.Register(plugins)
	certsigning.Register(plugins)
	certsubjectrestriction.Register(plugins)
}

// DefaultOffAdmissionPlugins get admission plugins off by default for kube-apiserver.
func DefaultOffAdmissionPlugins() sets.String {
	defaultOnPlugins := sets.NewString(
		lifecycle.PluginName,                    // NamespaceLifecycle
		limitranger.PluginName,                  // LimitRanger
		serviceaccount.PluginName,               // ServiceAccount
		setdefault.PluginName,                   // DefaultStorageClass
		resize.PluginName,                       // PersistentVolumeClaimResize
		defaulttolerationseconds.PluginName,     // DefaultTolerationSeconds
		mutatingwebhook.PluginName,              // MutatingAdmissionWebhook
		validatingwebhook.PluginName,            // ValidatingAdmissionWebhook
		resourcequota.PluginName,                // ResourceQuota
		storageobjectinuseprotection.PluginName, // StorageObjectInUseProtection
		podpriority.PluginName,                  // Priority
		nodetaint.PluginName,                    // TaintNodesByCondition
		runtimeclass.PluginName,                 // RuntimeClass
		certapproval.PluginName,                 // CertificateApproval
		certsigning.PluginName,                  // CertificateSigning
		certsubjectrestriction.PluginName,       // CertificateSubjectRestriction
		defaultingressclass.PluginName,          // DefaultIngressClass
		podsecurity.PluginName,                  // PodSecurity
	)

	return sets.NewString(AllOrderedPlugins...).Difference(defaultOnPlugins)
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Skaffold 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 tag

import (
	"bufio"
	"fmt"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	git "gopkg.in/src-d/go-git.v4"
	"gopkg.in/src-d/go-git.v4/plumbing"
)

// GitCommit tags an image by the git commit it was built at.
type GitCommit struct{}

func (c *GitCommit) Labels() map[string]string {
	return map[string]string{
		constants.Labels.TagPolicy: "git-commit",
	}
}

// GenerateFullyQualifiedImageName tags an image with the supplied image name and the git commit.
func (c *GitCommit) GenerateFullyQualifiedImageName(workingDir string, opts *Options) (string, error) {
	if _, err := exec.LookPath("git"); err == nil {
		return generateNameGitShellOut(workingDir, opts)
	}

	logrus.Warn("git binary not found. Falling back on a go git implementation. Some features might not work.")
	return generateNameGoGit(workingDir, opts)
}

func generateNameGitShellOut(workingDir string, opts *Options) (string, error) {
	root, err := runGit(workingDir, "rev-parse", "--show-toplevel")
	if err != nil {
		return fallbackOnDigest(opts, err), nil
	}

	commitHash, err := runGit(root, "rev-parse", "HEAD")
	if err != nil {
		return fallbackOnDigest(opts, err), nil
	}

	currentTag := commitHash[0:7]

	status, err := runGitLines(root, "status", "--porcelain")
	if err != nil {
		return "", errors.Wrap(err, "getting git status")
	}

	dirty, err := isDirty(root, workingDir, stripStatus(status))
	if err != nil {
		return "", errors.Wrap(err, "getting status for workingDir")
	}

	if dirty {
		return dirtyTag(currentTag, opts), nil
	}

	// Ignore error. It means there's no tag.
	tags, _ := runGitLines(root, "describe", "--tags", "--exact-match")

	return commitOrTag(currentTag, tags, opts), nil
}

func generateNameGoGit(workingDir string, opts *Options) (string, error) {
	repo, err := git.PlainOpenWithOptions(workingDir, &git.PlainOpenOptions{DetectDotGit: true})
	if err != nil {
		return fallbackOnDigest(opts, err), nil
	}

	w, err := repo.Worktree()
	if err != nil {
		return fallbackOnDigest(opts, err), nil
	}
	root := w.Filesystem.Root()

	head, err := repo.Head()
	if err != nil {
		return fallbackOnDigest(opts, err), nil
	}

	commitHash := head.Hash().String()
	currentTag := commitHash[0:7]

	status, err := w.Status()
	if err != nil {
		return "", errors.Wrap(err, "reading status")
	}

	dirty, err := isDirty(root, workingDir, changedPaths(status))
	if err != nil {
		return "", errors.Wrap(err, "getting status for workingDir")
	}

	if dirty {
		return dirtyTag(currentTag, opts), nil
	}

	tagrefs, err := repo.Tags()
	if err != nil {
		return "", errors.Wrap(err, "determining git tag")
	}

	var tags []string
	if err = tagrefs.ForEach(func(t *plumbing.Reference) error {
		if t.Hash() == head.Hash() {
			tags = append(tags, t.Name().Short())
		}
		return nil
	}); err != nil {
		return "", errors.Wrap(err, "determining git tag")
	}

	return commitOrTag(currentTag, tags, opts), nil
}

func runGit(workingDir string, arg ...string) (string, error) {
	cmd := exec.Command("git", arg...)
	cmd.Dir = workingDir

	out, err := util.RunCmdOut(cmd)
	if err != nil {
		return "", err
	}

	return strings.TrimSpace(string(out)), nil
}

func runGitLines(workingDir string, arg ...string) ([]string, error) {
	out, err := runGit(workingDir, arg...)
	if err != nil {
		return nil, err
	}

	var lines []string

	scanner := bufio.NewScanner(strings.NewReader(out))
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line != "" {
			lines = append(lines, line)
		}
	}

	return lines, nil
}

func commitOrTag(currentTag string, tags []string, opts *Options) string {
	if len(tags) > 0 {
		currentTag = tags[0]
	}

	return fmt.Sprintf("%s:%s", opts.ImageName, currentTag)
}

func stripStatus(lines []string) []string {
	var paths []string

	for _, line := range lines {
		path := strings.Fields(line)[1]
		paths = append(paths, path)
	}

	return paths
}

func isDirty(root, workingDir string, changes []string) (bool, error) {
	root, err := normalizePath(root)
	if err != nil {
		return false, errors.Wrap(err, "normalizing path")
	}

	absWorkingDir, err := normalizePath(workingDir)
	if err != nil {
		return false, errors.Wrap(err, "normalizing path")
	}

	for _, change := range changes {
		if strings.HasPrefix(filepath.Join(root, change), absWorkingDir) {
			return true, nil
		}
	}

	return false, nil
}

func shortDigest(opts *Options) string {
	return strings.TrimPrefix(opts.Digest, "sha256:")[0:7]
}

func dirtyTag(currentTag string, opts *Options) string {
	return fmt.Sprintf("%s:%s-dirty-%s", opts.ImageName, currentTag, shortDigest(opts))
}

func fallbackOnDigest(opts *Options, err error) string {
	logrus.Warnln("Using digest instead of git commit:", err)

	return fmt.Sprintf("%s:dirty-%s", opts.ImageName, shortDigest(opts))
}

func changedPaths(status git.Status) []string {
	var paths []string

	for path, change := range status {
		if change.Worktree != git.Unmodified {
			paths = append(paths, path)
		}
	}

	return paths
}

func normalizePath(path string) (string, error) {
	abs, err := filepath.Abs(path)
	if err != nil {
		return "", err
	}
	return filepath.EvalSymlinks(abs)
}
<commit_msg>Slightly faster git tagger<commit_after>/*
Copyright 2018 The Skaffold 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 tag

import (
	"bufio"
	"fmt"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	git "gopkg.in/src-d/go-git.v4"
	"gopkg.in/src-d/go-git.v4/plumbing"
)

// GitCommit tags an image by the git commit it was built at.
type GitCommit struct{}

func (c *GitCommit) Labels() map[string]string {
	return map[string]string{
		constants.Labels.TagPolicy: "git-commit",
	}
}

// GenerateFullyQualifiedImageName tags an image with the supplied image name and the git commit.
func (c *GitCommit) GenerateFullyQualifiedImageName(workingDir string, opts *Options) (string, error) {
	if _, err := exec.LookPath("git"); err == nil {
		return generateNameGitShellOut(workingDir, opts)
	}

	logrus.Warn("git binary not found. Falling back on a go git implementation. Some features might not work.")
	return generateNameGoGit(workingDir, opts)
}

func generateNameGitShellOut(workingDir string, opts *Options) (string, error) {
	rootAndHash, err := runGitLines(workingDir, "rev-parse", "--show-toplevel", "HEAD")
	if err != nil {
		return fallbackOnDigest(opts, err), nil
	}

	root := rootAndHash[0]
	commitHash := rootAndHash[1]

	currentTag := commitHash[0:7]

	status, err := runGitLines(root, "status", "--porcelain")
	if err != nil {
		return "", errors.Wrap(err, "getting git status")
	}

	dirty, err := isDirty(root, workingDir, stripStatus(status))
	if err != nil {
		return "", errors.Wrap(err, "getting status for workingDir")
	}

	if dirty {
		return dirtyTag(currentTag, opts), nil
	}

	// Ignore error. It means there's no tag.
	tags, _ := runGitLines(root, "describe", "--tags", "--exact-match")

	return commitOrTag(currentTag, tags, opts), nil
}

func generateNameGoGit(workingDir string, opts *Options) (string, error) {
	repo, err := git.PlainOpenWithOptions(workingDir, &git.PlainOpenOptions{DetectDotGit: true})
	if err != nil {
		return fallbackOnDigest(opts, err), nil
	}

	w, err := repo.Worktree()
	if err != nil {
		return fallbackOnDigest(opts, err), nil
	}
	root := w.Filesystem.Root()

	head, err := repo.Head()
	if err != nil {
		return fallbackOnDigest(opts, err), nil
	}

	commitHash := head.Hash().String()
	currentTag := commitHash[0:7]

	status, err := w.Status()
	if err != nil {
		return "", errors.Wrap(err, "reading status")
	}

	dirty, err := isDirty(root, workingDir, changedPaths(status))
	if err != nil {
		return "", errors.Wrap(err, "getting status for workingDir")
	}

	if dirty {
		return dirtyTag(currentTag, opts), nil
	}

	tagrefs, err := repo.Tags()
	if err != nil {
		return "", errors.Wrap(err, "determining git tag")
	}

	var tags []string
	if err = tagrefs.ForEach(func(t *plumbing.Reference) error {
		if t.Hash() == head.Hash() {
			tags = append(tags, t.Name().Short())
		}
		return nil
	}); err != nil {
		return "", errors.Wrap(err, "determining git tag")
	}

	return commitOrTag(currentTag, tags, opts), nil
}

func runGit(workingDir string, arg ...string) (string, error) {
	cmd := exec.Command("git", arg...)
	cmd.Dir = workingDir

	out, err := util.RunCmdOut(cmd)
	if err != nil {
		return "", err
	}

	return strings.TrimSpace(string(out)), nil
}

func runGitLines(workingDir string, arg ...string) ([]string, error) {
	out, err := runGit(workingDir, arg...)
	if err != nil {
		return nil, err
	}

	var lines []string

	scanner := bufio.NewScanner(strings.NewReader(out))
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line != "" {
			lines = append(lines, line)
		}
	}

	return lines, nil
}

func commitOrTag(currentTag string, tags []string, opts *Options) string {
	if len(tags) > 0 {
		currentTag = tags[0]
	}

	return fmt.Sprintf("%s:%s", opts.ImageName, currentTag)
}

func stripStatus(lines []string) []string {
	var paths []string

	for _, line := range lines {
		path := strings.Fields(line)[1]
		paths = append(paths, path)
	}

	return paths
}

func isDirty(root, workingDir string, changes []string) (bool, error) {
	root, err := normalizePath(root)
	if err != nil {
		return false, errors.Wrap(err, "normalizing path")
	}

	absWorkingDir, err := normalizePath(workingDir)
	if err != nil {
		return false, errors.Wrap(err, "normalizing path")
	}

	for _, change := range changes {
		if strings.HasPrefix(filepath.Join(root, change), absWorkingDir) {
			return true, nil
		}
	}

	return false, nil
}

func shortDigest(opts *Options) string {
	return strings.TrimPrefix(opts.Digest, "sha256:")[0:7]
}

func dirtyTag(currentTag string, opts *Options) string {
	return fmt.Sprintf("%s:%s-dirty-%s", opts.ImageName, currentTag, shortDigest(opts))
}

func fallbackOnDigest(opts *Options, err error) string {
	logrus.Warnln("Using digest instead of git commit:", err)

	return fmt.Sprintf("%s:dirty-%s", opts.ImageName, shortDigest(opts))
}

func changedPaths(status git.Status) []string {
	var paths []string

	for path, change := range status {
		if change.Worktree != git.Unmodified {
			paths = append(paths, path)
		}
	}

	return paths
}

func normalizePath(path string) (string, error) {
	abs, err := filepath.Abs(path)
	if err != nil {
		return "", err
	}
	return filepath.EvalSymlinks(abs)
}
<|endoftext|>