<commit_before>package kloud

import (
	"fmt"
	"strings"

	"koding/kites/kloud/eventer"
	"koding/kites/kloud/machinestate"
	"koding/kites/kloud/protocol"

	"github.com/koding/kite"
)

type ControlResult struct {
	State   machinestate.State `json:"state"`
	EventId string             `json:"eventId"`
}

type controlFunc func(*protocol.Machine, protocol.Provider) (interface{}, error)

type statePair struct {
	initial machinestate.State
	final   machinestate.State
}

var states = map[string]*statePair{
	"build":   &statePair{initial: machinestate.Building, final: machinestate.Running},
	"start":   &statePair{initial: machinestate.Starting, final: machinestate.Running},
	"stop":    &statePair{initial: machinestate.Stopping, final: machinestate.Stopped},
	"destroy": &statePair{initial: machinestate.Terminating, final: machinestate.Terminated},
	"restart": &statePair{initial: machinestate.Rebooting, final: machinestate.Running},
	"resize":  &statePair{initial: machinestate.Pending, final: machinestate.Running},
	"reinit":  &statePair{initial: machinestate.Terminating, final: machinestate.Running},
}

func (k *Kloud) Start(r *kite.Request) (resp interface{}, reqErr error) {
	startFunc := func(m *protocol.Machine, p protocol.Provider) (interface{}, error) {
		resp, err := p.Start(m)
		if err != nil {
			return nil, err
		}

		// some providers might provide empty information, therefore do not
		// update anything for them
		if resp == nil {
			return resp, nil
		}

		err = k.Storage.Update(m.Id, &StorageData{
			Type: "start",
			Data: map[string]interface{}{
				"ipAddress":    resp.IpAddress,
				"domainName":   resp.DomainName,
				"instanceId":   resp.InstanceId,
				"instanceName": resp.InstanceName,
			},
		})

		if err != nil {
			k.Log.Error("[%s] updating data after start method was not possible: %s",
				m.Id, err.Error())
		}

		// do not return the error, the machine is already prepared and
		// started, it should be ready
		return resp, nil
	}

	return k.coreMethods(r, startFunc)
}

func (k *Kloud) Resize(r *kite.Request) (reqResp interface{}, reqErr error) {
	resizeFunc := func(m *protocol.Machine, p protocol.Provider) (interface{}, error) {
		resp, err := p.Resize(m)
		if err != nil {
			return nil, err
		}

		// some providers might provide empty information, therefore do not
		// update anything for them
		if resp == nil {
			return resp, nil
		}

		err = k.Storage.Update(m.Id, &StorageData{
			Type: "resize",
			Data: map[string]interface{}{
				"ipAddress":    resp.IpAddress,
				"domainName":   resp.DomainName,
				"instanceId":   resp.InstanceId,
				"instanceName": resp.InstanceName,
			},
		})

		if err != nil {
			k.Log.Error("[%s] updating data after resize method was not possible: %s",
				m.Id, err.Error())
		}

		return resp, nil
	}

	return k.coreMethods(r, resizeFunc)
}

func (k *Kloud) Reinit(r *kite.Request) (resp interface{}, reqErr error) {
	reinitFunc := func(m *protocol.Machine, p protocol.Provider) (interface{}, error) {
		resp, err := p.Reinit(m)
		if err != nil {
			return nil, err
		}

		// some providers might provide empty information, therefore do not
		// update anything for them
		if resp == nil {
			return resp, nil
		}

		// if the username is not explicit changed, assign the original username to it
		if resp.Username == "" {
			resp.Username = m.Username
		}

		err = k.Storage.Update(m.Id, &StorageData{
			Type: "reinit",
			Data: map[string]interface{}{
				"ipAddress":    resp.IpAddress,
				"domainName":   resp.DomainName,
				"instanceId":   resp.InstanceId,
				"instanceName": resp.InstanceName,
				"queryString":  resp.KiteQuery,
			},
		})

		return resp, err
	}

	return k.coreMethods(r, reinitFunc)
}

func (k *Kloud) Stop(r *kite.Request) (resp interface{}, reqErr error) {
	stopFunc := func(m *protocol.Machine, p protocol.Provider) (interface{}, error) {
		err := p.Stop(m)
		if err != nil {
			return nil, err
		}

		err = k.Storage.Update(m.Id, &StorageData{
			Type: "stop",
			Data: map[string]interface{}{
				"ipAddress": "",
			},
		})

		return nil, err
	}

	return k.coreMethods(r, stopFunc)
}

func (k *Kloud) Restart(r *kite.Request) (resp interface{}, reqErr error) {
	restartFunc := func(m *protocol.Machine, p protocol.Provider) (interface{}, error) {
		err := p.Restart(m)
		return nil, err
	}

	return k.coreMethods(r, restartFunc)
}

func (k *Kloud) Destroy(r *kite.Request) (resp interface{}, reqErr error) {
	destroyFunc := func(m *protocol.Machine, p protocol.Provider) (interface{}, error) {
		err := p.Destroy(m)
		if err != nil {
			return nil, err
		}

		// purge the data too
		err = k.Storage.Delete(m.Id)
		return nil, err
	}

	return k.coreMethods(r, destroyFunc)
}

func (k *Kloud) Info(r *kite.Request) (interface{}, error) {
	machine, err := k.PrepareMachine(r)
	if err != nil {
		return nil, err
	}

	if machine.State == machinestate.NotInitialized {
		return &protocol.InfoArtifact{
			State: machinestate.NotInitialized,
			Name:  "not-initialized-instance",
		}, nil
	}

	// add fake eventer to avoid errors on NewClient at provider, the info method doesn't use it
	machine.Eventer = &eventer.Events{}

	provider, ok := k.providers[machine.Provider]
	if !ok {
		return nil, NewError(ErrProviderAvailable)
	}

	controller, ok := provider.(protocol.Provider)
	if !ok {
		return nil, NewError(ErrProviderNotImplemented)
	}

	response, err := controller.Info(machine)
	if err != nil {
		return nil, err
	}

	if response.State == machinestate.Unknown {
		response.State = machine.State
	}

	return response, nil
}

// coreMethods is running and returning the response for the given controlFunc.
// This method is used to avoid duplicate codes in many codes (because we do
// the same steps for each of them).
func (k *Kloud) coreMethods(r *kite.Request, fn controlFunc) (result interface{}, reqErr error) {
	machine, err := k.PrepareMachine(r)
	if err != nil {
		return nil, err
	}

	defer func() {
		if reqErr != nil {
			k.Locker.Unlock(machine.Id)
		}
	}()

	provider, ok := k.providers[machine.Provider]
	if !ok {
		return nil, NewError(ErrProviderAvailable)
	}

	controller, ok := provider.(protocol.Provider)
	if !ok {
		return nil, NewError(ErrProviderNotImplemented)
	}

	// Check if the given method is in valid methods of that current state. For
	// example if the method is "build", and the state is "stopped" than this
	// will return an error.
	if !methodIn(r.Method, machine.State.ValidMethods()...) {
		return nil, fmt.Errorf("method '%s' not allowed for current state '%s'. Allowed methods are: %v",
			r.Method, strings.ToLower(machine.State.String()), machine.State.ValidMethods())
	}

	// get our state pair. A state pair defines the initial and final state of
	// a method.  For example, for "restart" method the initial state is
	// "rebooting" and the final "running.
	s, ok := states[r.Method]
	if !ok {
		return nil, fmt.Errorf("no state pair available for %s", r.Method)
	}

	// now mark that we are starting...
	k.Storage.UpdateState(machine.Id, s.initial)

	// each method has his own unique eventer
	machine.Eventer = k.NewEventer(r.Method + "-" + machine.Id)

	// push the first event so it's filled with it, let people know that we're
	// starting.
	machine.Eventer.Push(&eventer.Event{Message: fmt.Sprintf("Starting %s", r.Method), Status: s.initial})

	// Start our core method in a goroutine to not block it for the client
	// side. However we do return an event id which is an unique for tracking
	// the current status of the running method.
	go func() {
		k.idlock.Get(machine.Id).Lock()
		defer k.idlock.Get(machine.Id).Unlock()

		status := s.final
		msg := fmt.Sprintf("%s is finished successfully.", r.Method)
		eventErr := ""

		_, err := fn(machine, controller)
		if err != nil {
			k.Log.Error("[%s] %s failed. State is set back to origin '%s'. err: %s",
				machine.Id, r.Method, machine.State, err.Error())

			status = machine.State
			msg = ""
			eventErr = fmt.Sprintf("%s failed. Please contact support.", r.Method)
		} else {
			k.Log.Info("[%s] ========== %s finished (status: %s) ==========",
				machine.Id, strings.ToUpper(r.Method), status)
		}

		// update final status in storage
		k.Storage.UpdateState(machine.Id, status)

		// update final status in storage
		machine.Eventer.Push(&eventer.Event{
			Message:    msg,
			Status:     status,
			Percentage: 100,
			Error:      eventErr,
		})

		// unlock distributed lock
		k.Locker.Unlock(machine.Id)
	}()

	return ControlResult{
		EventId: machine.Eventer.Id(),
		State:   s.initial,
	}, nil
}

func (k *Kloud) PrepareMachine(r *kite.Request) (resp *protocol.Machine, reqErr error) {
	// calls with zero arguments causes args to be nil. Check it that we
	// don't get a beloved panic
	if r.Args == nil {
		return nil, NewError(ErrNoArguments)
	}

	var args struct {
		MachineId string
	}

	if err := r.Args.One().Unmarshal(&args); err != nil {
		return nil, err
	}

	defer func() {
		if reqErr != nil {
			k.Log.Error("[%s] method '%s' failed. err: %s", args.MachineId, r.Method, reqErr.Error())
		}
	}()

	k.Log.Info("[%s] ========== %s called by user: %s ==========",
		args.MachineId, strings.ToUpper(r.Method), r.Username)

	if args.MachineId == "" {
		return nil, NewError(ErrMachineIdMissing)
	}

	// Lock the machine id so no one else can access it. It means this
	// kloud instance is now responsible for this machine id. Its basically
	// a distributed lock. It's unlocked when there is an error or if the
	// method call is finished (unlocking is done inside the responsible
	// method calls).
	if r.Method != "info" {
		if err := k.Locker.Lock(args.MachineId); err != nil {
			return nil, err
		}

		// if something goes wrong after step reset the document which is was
		// set in the by previous step by Locker.Lock(). If there is no error,
		// the lock will be unlocked in the respective method  function.
		defer func() {
			if reqErr != nil {
				// otherwise that means Locker.Lock or something else in
				// ControlFunc failed. Reset the lock again so it can be acquired by
				// others.
				k.Locker.Unlock(args.MachineId)
			}
		}()
	}

	// Get all the data we need.
	machine, err := k.Storage.Get(args.MachineId)
	if err != nil {
		return nil, err
	}

	if machine.Username == "" {
		return nil, NewError(ErrSignUsernameEmpty)
	}

	return machine, nil
}

// methodIn checks if the method exist in the given methods
func methodIn(method string, methods ...string) bool {
	for _, m := range methods {
		if method == m {
			return true
		}
	}
	return false
}
<commit_msg>kloud/controller: don't display any error<commit_after>package kloud

import (
	"fmt"
	"strings"

	"koding/kites/kloud/eventer"
	"koding/kites/kloud/machinestate"
	"koding/kites/kloud/protocol"

	"github.com/koding/kite"
)

type ControlResult struct {
	State   machinestate.State `json:"state"`
	EventId string             `json:"eventId"`
}

type controlFunc func(*protocol.Machine, protocol.Provider) (interface{}, error)

type statePair struct {
	initial machinestate.State
	final   machinestate.State
}

var states = map[string]*statePair{
	"build":   &statePair{initial: machinestate.Building, final: machinestate.Running},
	"start":   &statePair{initial: machinestate.Starting, final: machinestate.Running},
	"stop":    &statePair{initial: machinestate.Stopping, final: machinestate.Stopped},
	"destroy": &statePair{initial: machinestate.Terminating, final: machinestate.Terminated},
	"restart": &statePair{initial: machinestate.Rebooting, final: machinestate.Running},
	"resize":  &statePair{initial: machinestate.Pending, final: machinestate.Running},
	"reinit":  &statePair{initial: machinestate.Terminating, final: machinestate.Running},
}

func (k *Kloud) Start(r *kite.Request) (resp interface{}, reqErr error) {
	startFunc := func(m *protocol.Machine, p protocol.Provider) (interface{}, error) {
		resp, err := p.Start(m)
		if err != nil {
			return nil, err
		}

		// some providers might provide empty information, therefore do not
		// update anything for them
		if resp == nil {
			return resp, nil
		}

		err = k.Storage.Update(m.Id, &StorageData{
			Type: "start",
			Data: map[string]interface{}{
				"ipAddress":    resp.IpAddress,
				"domainName":   resp.DomainName,
				"instanceId":   resp.InstanceId,
				"instanceName": resp.InstanceName,
			},
		})

		if err != nil {
			k.Log.Error("[%s] updating data after start method was not possible: %s",
				m.Id, err.Error())
		}

		// do not return the error, the machine is already prepared and
		// started, it should be ready
		return resp, nil
	}

	return k.coreMethods(r, startFunc)
}

func (k *Kloud) Resize(r *kite.Request) (reqResp interface{}, reqErr error) {
	resizeFunc := func(m *protocol.Machine, p protocol.Provider) (interface{}, error) {
		resp, err := p.Resize(m)
		if err != nil {
			return nil, err
		}

		// some providers might provide empty information, therefore do not
		// update anything for them
		if resp == nil {
			return resp, nil
		}

		err = k.Storage.Update(m.Id, &StorageData{
			Type: "resize",
			Data: map[string]interface{}{
				"ipAddress":    resp.IpAddress,
				"domainName":   resp.DomainName,
				"instanceId":   resp.InstanceId,
				"instanceName": resp.InstanceName,
			},
		})

		if err != nil {
			k.Log.Error("[%s] updating data after resize method was not possible: %s",
				m.Id, err.Error())
		}

		return resp, nil
	}

	return k.coreMethods(r, resizeFunc)
}

func (k *Kloud) Reinit(r *kite.Request) (resp interface{}, reqErr error) {
	reinitFunc := func(m *protocol.Machine, p protocol.Provider) (interface{}, error) {
		resp, err := p.Reinit(m)
		if err != nil {
			return nil, err
		}

		// some providers might provide empty information, therefore do not
		// update anything for them
		if resp == nil {
			return resp, nil
		}

		// if the username is not explicit changed, assign the original username to it
		if resp.Username == "" {
			resp.Username = m.Username
		}

		err = k.Storage.Update(m.Id, &StorageData{
			Type: "reinit",
			Data: map[string]interface{}{
				"ipAddress":    resp.IpAddress,
				"domainName":   resp.DomainName,
				"instanceId":   resp.InstanceId,
				"instanceName": resp.InstanceName,
				"queryString":  resp.KiteQuery,
			},
		})

		return resp, err
	}

	return k.coreMethods(r, reinitFunc)
}

func (k *Kloud) Stop(r *kite.Request) (resp interface{}, reqErr error) {
	stopFunc := func(m *protocol.Machine, p protocol.Provider) (interface{}, error) {
		err := p.Stop(m)
		if err != nil {
			return nil, err
		}

		err = k.Storage.Update(m.Id, &StorageData{
			Type: "stop",
			Data: map[string]interface{}{
				"ipAddress": "",
			},
		})

		return nil, err
	}

	return k.coreMethods(r, stopFunc)
}

func (k *Kloud) Restart(r *kite.Request) (resp interface{}, reqErr error) {
	restartFunc := func(m *protocol.Machine, p protocol.Provider) (interface{}, error) {
		err := p.Restart(m)
		return nil, err
	}

	return k.coreMethods(r, restartFunc)
}

func (k *Kloud) Destroy(r *kite.Request) (resp interface{}, reqErr error) {
	destroyFunc := func(m *protocol.Machine, p protocol.Provider) (interface{}, error) {
		err := p.Destroy(m)
		if err != nil {
			return nil, err
		}

		// purge the data too
		err = k.Storage.Delete(m.Id)
		return nil, err
	}

	return k.coreMethods(r, destroyFunc)
}

func (k *Kloud) Info(r *kite.Request) (interface{}, error) {
	machine, err := k.PrepareMachine(r)
	if err != nil {
		return nil, err
	}

	if machine.State == machinestate.NotInitialized {
		return &protocol.InfoArtifact{
			State: machinestate.NotInitialized,
			Name:  "not-initialized-instance",
		}, nil
	}

	// add fake eventer to avoid errors on NewClient at provider, the info method doesn't use it
	machine.Eventer = &eventer.Events{}

	provider, ok := k.providers[machine.Provider]
	if !ok {
		return nil, NewError(ErrProviderAvailable)
	}

	controller, ok := provider.(protocol.Provider)
	if !ok {
		return nil, NewError(ErrProviderNotImplemented)
	}

	response, err := controller.Info(machine)
	if err != nil {
		return nil, err
	}

	if response.State == machinestate.Unknown {
		response.State = machine.State
	}

	return response, nil
}

// coreMethods is running and returning the response for the given controlFunc.
// This method is used to avoid duplicate codes in many codes (because we do
// the same steps for each of them).
func (k *Kloud) coreMethods(r *kite.Request, fn controlFunc) (result interface{}, reqErr error) {
	machine, err := k.PrepareMachine(r)
	if err != nil {
		return nil, err
	}

	defer func() {
		if reqErr != nil {
			k.Locker.Unlock(machine.Id)
		}
	}()

	provider, ok := k.providers[machine.Provider]
	if !ok {
		return nil, NewError(ErrProviderAvailable)
	}

	controller, ok := provider.(protocol.Provider)
	if !ok {
		return nil, NewError(ErrProviderNotImplemented)
	}

	// Check if the given method is in valid methods of that current state. For
	// example if the method is "build", and the state is "stopped" than this
	// will return an error.
	if !methodIn(r.Method, machine.State.ValidMethods()...) {
		return nil, fmt.Errorf("method '%s' not allowed for current state '%s'. Allowed methods are: %v",
			r.Method, strings.ToLower(machine.State.String()), machine.State.ValidMethods())
	}

	// get our state pair. A state pair defines the initial and final state of
	// a method.  For example, for "restart" method the initial state is
	// "rebooting" and the final "running.
	s, ok := states[r.Method]
	if !ok {
		return nil, fmt.Errorf("no state pair available for %s", r.Method)
	}

	// now mark that we are starting...
	k.Storage.UpdateState(machine.Id, s.initial)

	// each method has his own unique eventer
	machine.Eventer = k.NewEventer(r.Method + "-" + machine.Id)

	// push the first event so it's filled with it, let people know that we're
	// starting.
	machine.Eventer.Push(&eventer.Event{Message: fmt.Sprintf("Starting %s", r.Method), Status: s.initial})

	// Start our core method in a goroutine to not block it for the client
	// side. However we do return an event id which is an unique for tracking
	// the current status of the running method.
	go func() {
		k.idlock.Get(machine.Id).Lock()
		defer k.idlock.Get(machine.Id).Unlock()

		status := s.final
		msg := fmt.Sprintf("%s is finished successfully.", r.Method)
		eventErr := ""

		_, err := fn(machine, controller)
		if err != nil {
			k.Log.Error("[%s] %s failed. State is set back to origin '%s'. err: %s",
				machine.Id, r.Method, machine.State, err.Error())

			status = machine.State
			msg = ""
			eventErr = fmt.Sprintf("%s failed. Please contact support.", r.Method)
		} else {
			k.Log.Info("[%s] ========== %s finished (status: %s) ==========",
				machine.Id, strings.ToUpper(r.Method), status)
		}

		// update final status in storage
		k.Storage.UpdateState(machine.Id, status)

		// update final status in storage
		machine.Eventer.Push(&eventer.Event{
			Message:    msg,
			Status:     status,
			Percentage: 100,
			Error:      eventErr,
		})

		// unlock distributed lock
		k.Locker.Unlock(machine.Id)
	}()

	return ControlResult{
		EventId: machine.Eventer.Id(),
		State:   s.initial,
	}, nil
}

func (k *Kloud) PrepareMachine(r *kite.Request) (resp *protocol.Machine, reqErr error) {
	// calls with zero arguments causes args to be nil. Check it that we
	// don't get a beloved panic
	if r.Args == nil {
		return nil, NewError(ErrNoArguments)
	}

	var args struct {
		MachineId string
	}

	if err := r.Args.One().Unmarshal(&args); err != nil {
		return nil, err
	}

	k.Log.Info("[%s] ========== %s called by user: %s ==========",
		args.MachineId, strings.ToUpper(r.Method), r.Username)

	if args.MachineId == "" {
		return nil, NewError(ErrMachineIdMissing)
	}

	// Lock the machine id so no one else can access it. It means this
	// kloud instance is now responsible for this machine id. Its basically
	// a distributed lock. It's unlocked when there is an error or if the
	// method call is finished (unlocking is done inside the responsible
	// method calls).
	if r.Method != "info" {
		if err := k.Locker.Lock(args.MachineId); err != nil {
			return nil, err
		}

		// if something goes wrong after step reset the document which is was
		// set in the by previous step by Locker.Lock(). If there is no error,
		// the lock will be unlocked in the respective method  function.
		defer func() {
			if reqErr != nil {
				// otherwise that means Locker.Lock or something else in
				// ControlFunc failed. Reset the lock again so it can be acquired by
				// others.
				k.Locker.Unlock(args.MachineId)
			}
		}()
	}

	// Get all the data we need.
	machine, err := k.Storage.Get(args.MachineId)
	if err != nil {
		return nil, err
	}

	if machine.Username == "" {
		return nil, NewError(ErrSignUsernameEmpty)
	}

	return machine, nil
}

// methodIn checks if the method exist in the given methods
func methodIn(method string, methods ...string) bool {
	for _, m := range methods {
		if method == m {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>package throttler

import (
	"testing"
	"time"
)

// TestReplicationLagCache tests the wrapping of the ring buffer.
// Other parts of the code are already covered by max_replication_lag_module_test.go.
func TestReplicationLagCache(t *testing.T) {
	c := newReplicationLagCache(2)
	r1Key := tabletStats(r1, 1).Key

	// If there is no entry yet, a zero struct is returned.
	zeroEntry := c.atOrAfter(r1Key, sinceZero(0*time.Second))
	if !zeroEntry.isZero() {
		t.Fatalf("atOrAfter(<non existant key>) should have returned a zero entry but did not: %v", zeroEntry)
	}

	// First entry at 1s.
	c.add(lagRecord(sinceZero(1*time.Second), r1, 1))
	if got, want := c.latest(r1Key).time, sinceZero(1*time.Second); got != want {
		t.Fatalf("latest(r1) = %v, want = %v", got, want)
	}

	// Second entry at 2s makes the cache full.
	c.add(lagRecord(sinceZero(2*time.Second), r1, 2))
	if got, want := c.latest(r1Key).time, sinceZero(2*time.Second); got != want {
		t.Fatalf("latest(r1) = %v, want = %v", got, want)
	}
	if got, want := c.atOrAfter(r1Key, sinceZero(1*time.Second)).time, sinceZero(1*time.Second); got != want {
		t.Fatalf("atOrAfter(r1) = %v, want = %v", got, want)
	}

	// Third entry at 3s evicts the 1s entry.
	c.add(lagRecord(sinceZero(3*time.Second), r1, 3))
	if got, want := c.latest(r1Key).time, sinceZero(3*time.Second); got != want {
		t.Fatalf("latest(r1) = %v, want = %v", got, want)
	}
	// Requesting an entry at 1s or after gets us the entry for 2s.
	if got, want := c.atOrAfter(r1Key, sinceZero(1*time.Second)).time, sinceZero(2*time.Second); got != want {
		t.Fatalf("atOrAfter(r1) = %v, want = %v", got, want)
	}

	// Wrap around one more time. Entries at 4s and 5s should be left.
	c.add(lagRecord(sinceZero(4*time.Second), r1, 4))
	c.add(lagRecord(sinceZero(5*time.Second), r1, 5))
	if got, want := c.latest(r1Key).time, sinceZero(5*time.Second); got != want {
		t.Fatalf("latest(r1) = %v, want = %v", got, want)
	}
	if got, want := c.atOrAfter(r1Key, sinceZero(1*time.Second)).time, sinceZero(4*time.Second); got != want {
		t.Fatalf("atOrAfter(r1) = %v, want = %v", got, want)
	}
}

func TestReplicationLagCache_SortByLag(t *testing.T) {
	c := newReplicationLagCache(2)
	r1Key := tabletStats(r1, 1).Key

	c.add(lagRecord(sinceZero(1*time.Second), r1, 30))
	c.sortByLag(1 /* ignoreNSlowestReplicas */, 30 /* minimumReplicationLag */)

	if c.slowReplicas[r1Key] {
		t.Fatal("the only replica tracked should not get ignored")
	}

	c.add(lagRecord(sinceZero(1*time.Second), r2, 1))
	c.sortByLag(1 /* ignoreNSlowestReplicas */, 1 /* minimumReplicationLag */)

	if !c.slowReplicas[r1Key] {
		t.Fatal("r1 should be tracked as a slow replica")
	}
}
<commit_msg>throttler: Clarify test comment. (#2001)<commit_after>package throttler

import (
	"testing"
	"time"
)

// TestReplicationLagCache tests that the ring buffer in "replicationLagHistory"
// wraps around correctly.
// Other parts of the code are already covered by
// max_replication_lag_module_test.go.
func TestReplicationLagCache(t *testing.T) {
	c := newReplicationLagCache(2)
	r1Key := tabletStats(r1, 1).Key

	// If there is no entry yet, a zero struct is returned.
	zeroEntry := c.atOrAfter(r1Key, sinceZero(0*time.Second))
	if !zeroEntry.isZero() {
		t.Fatalf("atOrAfter(<non existant key>) should have returned a zero entry but did not: %v", zeroEntry)
	}

	// First entry at 1s.
	c.add(lagRecord(sinceZero(1*time.Second), r1, 1))
	if got, want := c.latest(r1Key).time, sinceZero(1*time.Second); got != want {
		t.Fatalf("latest(r1) = %v, want = %v", got, want)
	}

	// Second entry at 2s makes the cache full.
	c.add(lagRecord(sinceZero(2*time.Second), r1, 2))
	if got, want := c.latest(r1Key).time, sinceZero(2*time.Second); got != want {
		t.Fatalf("latest(r1) = %v, want = %v", got, want)
	}
	if got, want := c.atOrAfter(r1Key, sinceZero(1*time.Second)).time, sinceZero(1*time.Second); got != want {
		t.Fatalf("atOrAfter(r1) = %v, want = %v", got, want)
	}

	// Third entry at 3s evicts the 1s entry.
	c.add(lagRecord(sinceZero(3*time.Second), r1, 3))
	if got, want := c.latest(r1Key).time, sinceZero(3*time.Second); got != want {
		t.Fatalf("latest(r1) = %v, want = %v", got, want)
	}
	// Requesting an entry at 1s or after gets us the entry for 2s.
	if got, want := c.atOrAfter(r1Key, sinceZero(1*time.Second)).time, sinceZero(2*time.Second); got != want {
		t.Fatalf("atOrAfter(r1) = %v, want = %v", got, want)
	}

	// Wrap around one more time. Entries at 4s and 5s should be left.
	c.add(lagRecord(sinceZero(4*time.Second), r1, 4))
	c.add(lagRecord(sinceZero(5*time.Second), r1, 5))
	if got, want := c.latest(r1Key).time, sinceZero(5*time.Second); got != want {
		t.Fatalf("latest(r1) = %v, want = %v", got, want)
	}
	if got, want := c.atOrAfter(r1Key, sinceZero(1*time.Second)).time, sinceZero(4*time.Second); got != want {
		t.Fatalf("atOrAfter(r1) = %v, want = %v", got, want)
	}
}

func TestReplicationLagCache_SortByLag(t *testing.T) {
	c := newReplicationLagCache(2)
	r1Key := tabletStats(r1, 1).Key

	c.add(lagRecord(sinceZero(1*time.Second), r1, 30))
	c.sortByLag(1 /* ignoreNSlowestReplicas */, 30 /* minimumReplicationLag */)

	if c.slowReplicas[r1Key] {
		t.Fatal("the only replica tracked should not get ignored")
	}

	c.add(lagRecord(sinceZero(1*time.Second), r2, 1))
	c.sortByLag(1 /* ignoreNSlowestReplicas */, 1 /* minimumReplicationLag */)

	if !c.slowReplicas[r1Key] {
		t.Fatal("r1 should be tracked as a slow replica")
	}
}
<|endoftext|>


<commit_before>package slack

import (
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"image/png"
	"io/ioutil"
	"log"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/netflix/hal-9001/hal"
	"github.com/nlopes/slack"
)

// Broker interacts with the slack service.
// TODO: consider using the hal.Cache() for [iuc]2[iuc]
// TODO: add a miss cache to avoid hammering the room/user info apis
type Broker struct {
	Client  *slack.Client     // slack API object
	RTM     *slack.RTM        // slack RTM object
	inst    string            // broker instance name
	i2u     map[string]string // id->name cache
	i2c     map[string]string // id->name cache
	u2i     map[string]string // name->id cache
	c2i     map[string]string // name->id cache
	idRegex *regexp.Regexp    // compiled RE to match user/room ids
}

type Config struct {
	Token string
}

func (c Config) NewBroker(name string) Broker {
	client := slack.New(c.Token)
	// TODO: check for failures and log.Fatalf()
	rtm := client.NewRTM()

	sb := Broker{
		Client:  client,
		RTM:     rtm,
		inst:    name,
		i2u:     make(map[string]string),
		i2c:     make(map[string]string),
		u2i:     make(map[string]string),
		c2i:     make(map[string]string),
		idRegex: regexp.MustCompile("^[UC][A-Z0-9]{8}$"),
	}

	// fill the caches at startup to cut down on API requests
	sb.FillUserCache()
	sb.FillRoomCache()

	go rtm.ManageConnection()

	return sb
}

// Name returns the name of the broker as set in NewBroker.
func (sb Broker) Name() string {
	return sb.inst
}

func (sb Broker) Send(evt hal.Evt) {
	om := sb.RTM.NewOutgoingMessage(evt.Body, evt.RoomId)
	sb.RTM.SendMessage(om)
}

func (sb Broker) SendTable(evt hal.Evt, hdr []string, rows [][]string) {
	out := evt.Clone()
	out.Body = hal.Utf8Table(hdr, rows)
	sb.SendAsImage(out)
}

// SendAsImage sends the body of the event as a png file. The png is rendered
// using hal's FixedFont facility.
// This is useful for making sure pre-formatted text stays legible in
// Slack while we wait for them to figure out a way to render things like
// tables of data consistently.
func (sb Broker) SendAsImage(evt hal.Evt) {
	fd := hal.FixedFont()

	// create a tempfile
	f, err := ioutil.TempFile(os.TempDir(), "hal")
	if err != nil {
		evt.Replyf("Could not create tempfile for image upload: %s", err)
		return
	}
	defer os.Remove(f.Name())

	// check for a color preference
	// need to figure out a way to have a helper around this
	var fg color.Color
	fg = color.Black
	// TODO: prefs --set isn't setting the room, etc. remove the filter for now
	fgprefs := hal.FindPrefs("", "", "", "", "image.fg")
	ufgprefs := fgprefs.User(evt.UserId)
	if len(ufgprefs) > 0 {
		fg = fd.ParseColor(ufgprefs[0].Value, fg)
	} else if len(fgprefs) > 0 {
		fg = fd.ParseColor(fgprefs[0].Value, fg)
	}

	var bg color.Color
	bg = color.Transparent
	// TODO: ditto from ft
	//bgprefs := hal.FindPrefs("", sb.Name(), evt.RoomId, "", "image.bg")
	bgprefs := hal.FindPrefs("", "", "", "", "image.bg")
	ubgprefs := bgprefs.User(evt.UserId)
	if len(ubgprefs) > 0 {
		bg = fd.ParseColor(ubgprefs[0].Value, fg)
	} else if len(bgprefs) > 0 {
		bg = fd.ParseColor(bgprefs[0].Value, fg)
	}

	// generate the image
	lines := strings.Split(strings.TrimSpace(evt.Body), "\n")
	textimg := fd.StringsToImage(lines, fg)

	// img has a background color, copy textimg onto it
	img := image.NewRGBA(textimg.Bounds())
	draw.Draw(img, img.Bounds(), &image.Uniform{bg}, image.ZP, draw.Src)
	draw.Draw(img, img.Bounds(), textimg, image.ZP, draw.Src)

	// TODO: apply background color

	// write the png data to the temp file
	png.Encode(f, img)
	f.Close()

	// upload the file
	params := slack.FileUploadParameters{
		File:     f.Name(),
		Filename: "text.png",
		Channels: []string{evt.RoomId},
	}
	_, err = sb.Client.UploadFile(params)
	if err != nil {
		evt.Replyf("Could not upload image: %s", err)
	}
}

// checks the cache to see if the room is known to this broker
func (sb Broker) HasRoom(room string) bool {
	if sb.idRegex.MatchString(room) {
		_, exists := sb.i2c[room]
		return exists
	} else {
		_, exists := sb.c2i[room]
		return exists
	}
}

// Stream is an event loop for Slack events & messages from the RTM API.
// Events are copied to a hal.Evt and forwarded to the exchange where they
// can be processed by registered handlers.
func (sb Broker) Stream(out chan *hal.Evt) {
	for {
		select {
		case msg := <-sb.RTM.IncomingEvents:
			switch ev := msg.Data.(type) {
			case *slack.HelloEvent:
				log.Println("brokers/slack ignoring HelloEvent")

			case *slack.ConnectedEvent:
				log.Printf("brokers/slack ignoring ConnectedEvent")

			case *slack.MessageEvent:
				m := msg.Data.(*slack.MessageEvent)
				// slack channels = hal rooms, see hal-9001/hal/event.go
				e := hal.Evt{
					Body:     m.Text,
					Room:     sb.RoomIdToName(m.Channel),
					RoomId:   m.Channel,
					User:     sb.UserIdToName(m.User),
					UserId:   m.User,
					Broker:   sb,
					Time:     slackTime(m.Timestamp),
					Original: m,
				}

				out <- &e

			case *slack.StarAddedEvent:
				sae := msg.Data.(*slack.StarAddedEvent)
				user := sb.UserIdToName(sae.User)

				e := hal.Evt{
					Body:     fmt.Sprintf("%q added a star", user),
					Room:     sb.RoomIdToName(sae.Item.Channel),
					RoomId:   sae.Item.Channel,
					User:     user,
					UserId:   sae.User,
					Broker:   sb,
					Time:     slackTime(sae.EventTimestamp),
					Original: sae,
				}

				out <- &e

			case *slack.StarRemovedEvent:
				sre := msg.Data.(*slack.StarRemovedEvent)
				user := sb.UserIdToName(sre.User)

				e := hal.Evt{
					Body:     fmt.Sprintf("%q removed a star", user),
					Room:     sb.RoomIdToName(sre.Item.Channel),
					RoomId:   sre.Item.Channel,
					User:     user,
					UserId:   sre.User,
					Broker:   sb,
					Time:     slackTime(sre.EventTimestamp),
					Original: sre,
				}

				out <- &e

			case *slack.ReactionAddedEvent:
				rae := msg.Data.(*slack.ReactionAddedEvent)
				user := sb.UserIdToName(rae.User)

				e := hal.Evt{
					Body:     fmt.Sprintf("%q added reaction %q", user, rae.Reaction),
					Room:     sb.RoomIdToName(rae.Item.Channel),
					RoomId:   rae.Item.Channel,
					User:     user,
					UserId:   rae.User,
					Broker:   sb,
					Time:     slackTime(rae.EventTimestamp),
					Original: rae,
				}

				out <- &e

			case *slack.ReactionRemovedEvent:
				rre := msg.Data.(*slack.ReactionRemovedEvent)
				user := sb.UserIdToName(rre.User)

				e := hal.Evt{
					Body:     fmt.Sprintf("%q removed reaction %q", user, rre.Reaction),
					Room:     sb.RoomIdToName(rre.Item.Channel),
					RoomId:   rre.Item.Channel,
					User:     user,
					UserId:   rre.User,
					Broker:   sb,
					Time:     slackTime(rre.EventTimestamp),
					Original: rre,
				}

				out <- &e

			case *slack.PresenceChangeEvent:
				// ignored

			case *slack.LatencyReport:
				// ignored

			case *slack.RTMError:
				log.Printf("brokers/slack ignoring RTMError: %s\n", ev.Error())

			case *slack.InvalidAuthEvent:
				log.Println("brokers/slack InvalidAuthEvent")
				break

			default:
				log.Printf("brokers/slack: unexpected message: %+v\n", msg)
			}
		}
	}
}

// slackTime converts the timestamp string to time.Time
// cribbed from: https://github.com/nlopes/slack/commit/17d746b30caa733b519f79fe372fd509bd6fc9fd
func slackTime(t string) time.Time {
	if t == "" {
		return time.Now()
	}

	floatN, err := strconv.ParseFloat(t, 64)
	if err != nil {
		log.Println("brokers/slack error parsing Slack time string %q:", t, err)
		return time.Now()
	}

	return time.Unix(int64(floatN), 0)
}

func (sb *Broker) FillUserCache() {
	users, err := sb.Client.GetUsers()
	if err != nil {
		log.Printf("brokers/slack failed to fetch user list: %s", err)
		return
	}

	for _, user := range users {
		sb.u2i[user.Name] = user.ID
		sb.i2u[user.ID] = user.Name
	}
}

func (sb *Broker) FillRoomCache() {
	rooms, err := sb.Client.GetChannels(true)
	if err != nil {
		log.Printf("brokers/slack failed to fetch room list: %s", err)
		return
	}

	for _, room := range rooms {
		sb.c2i[room.Name] = room.ID
		sb.i2c[room.ID] = room.Name
	}
}

// UserIdToName gets the human-readable username for a user ID using an
// in-memory cache that falls through to the Slack API
func (sb Broker) UserIdToName(id string) string {
	if id == "" {
		log.Println("broker/slack/UserIdToName(): Cannot look up empty string!")
		return ""
	}

	if name, exists := sb.i2u[id]; exists {
		return name
	} else {
		user, err := sb.Client.GetUserInfo(id)
		if err != nil {
			log.Printf("brokers/slack could not retrieve user info for '%s' via API: %s\n", id, err)
			return ""
		}

		// TODO: verify if room/user names are enforced unique in slack or if this is madness
		// remove this if it proves unnecessary (tobert/2016-03-02)
		if _, exists := sb.u2i[user.Name]; exists {
			if sb.u2i[user.Name] != user.ID {
				log.Fatalf("BUG(brokers/slack): found a non-unique user name:ID pair. Had: %q/%q. Got: %q/%q",
					user.Name, sb.u2i[user.Name], user.Name, user.ID)
			}
		}

		sb.i2u[user.ID] = user.Name
		sb.i2u[user.Name] = user.ID

		return user.Name
	}
}

// RoomIdToName gets the human-readable room name for a user ID using an
// in-memory cache that falls through to the Slack API
func (sb Broker) RoomIdToName(id string) string {
	if id == "" {
		log.Println("broker/slack/RoomIdToName(): Cannot look up empty string!")
		return ""
	}

	if name, exists := sb.i2c[id]; exists {
		return name
	} else {
		var name string

		// private channels are on a different endpoint
		if strings.HasPrefix(id, "G") {
			grp, err := sb.Client.GetGroupInfo(id)
			if err != nil {
				log.Printf("brokers/slack could not retrieve group info for '%s' via API: %s\n", id, err)
				return ""
			}
			name = grp.Name
		} else {
			room, err := sb.Client.GetChannelInfo(id)
			if err != nil {
				log.Printf("brokers/slack could not retrieve room info for '%s' via API: %s\n", id, err)
				return ""
			}
			name = room.Name
		}

		// TODO: verify if room/user names are enforced unique in slack or if this is madness
		// remove this if it proves unnecessary (tobert/2016-03-02)
		if _, exists := sb.c2i[name]; exists {
			if sb.c2i[name] != id {
				log.Fatalf("BUG(brokers/slack): found a non-unique room name:ID pair. Had: %q/%q. Got: %q/%q",
					name, sb.c2i[name], name, id)
			}
		}

		sb.i2c[id] = name
		sb.c2i[name] = id

		return name
	}
}

// UserNameToId gets the human-readable username for a user ID using an
// in-memory cache that falls through to the Slack API
func (sb Broker) UserNameToId(name string) string {
	if name == "" {
		log.Println("broker/slack/UserNameToId(): Cannot look up empty string!")
		return ""
	}

	if id, exists := sb.u2i[name]; exists {
		return id
	} else {
		// there doesn't seem to be a name->id lookup so refresh the cache
		// and try again if we get here
		sb.FillUserCache()
		if id, exists := sb.u2i[name]; exists {
			return id
		}

		log.Printf("brokers/slack service does not seem to have knowledge of username %q", name)
		return ""
	}
}

// RoomNameToId gets the human-readable room name for a user ID using an
// in-memory cache that falls through to the Slack API
func (sb Broker) RoomNameToId(name string) string {
	if name == "" {
		log.Println("broker/slack/RoomNameToId(): Cannot look up empty string!")
		return ""
	}

	if id, exists := sb.c2i[name]; exists {
		return id
	} else {
		sb.FillRoomCache()
		if id, exists := sb.c2i[name]; exists {
			return id
		}

		log.Printf("brokers/slack service does not seem to have knowledge of room name %q", name)
		return ""
	}
}
<commit_msg>rewrite time parsing to be more accurate<commit_after>package slack

import (
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"image/png"
	"io/ioutil"
	"log"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/netflix/hal-9001/hal"
	"github.com/nlopes/slack"
)

// Broker interacts with the slack service.
// TODO: consider using the hal.Cache() for [iuc]2[iuc]
// TODO: add a miss cache to avoid hammering the room/user info apis
type Broker struct {
	Client  *slack.Client     // slack API object
	RTM     *slack.RTM        // slack RTM object
	inst    string            // broker instance name
	i2u     map[string]string // id->name cache
	i2c     map[string]string // id->name cache
	u2i     map[string]string // name->id cache
	c2i     map[string]string // name->id cache
	idRegex *regexp.Regexp    // compiled RE to match user/room ids
}

type Config struct {
	Token string
}

func (c Config) NewBroker(name string) Broker {
	client := slack.New(c.Token)
	// TODO: check for failures and log.Fatalf()
	rtm := client.NewRTM()

	sb := Broker{
		Client:  client,
		RTM:     rtm,
		inst:    name,
		i2u:     make(map[string]string),
		i2c:     make(map[string]string),
		u2i:     make(map[string]string),
		c2i:     make(map[string]string),
		idRegex: regexp.MustCompile("^[UC][A-Z0-9]{8}$"),
	}

	// fill the caches at startup to cut down on API requests
	sb.FillUserCache()
	sb.FillRoomCache()

	go rtm.ManageConnection()

	return sb
}

// Name returns the name of the broker as set in NewBroker.
func (sb Broker) Name() string {
	return sb.inst
}

func (sb Broker) Send(evt hal.Evt) {
	om := sb.RTM.NewOutgoingMessage(evt.Body, evt.RoomId)
	sb.RTM.SendMessage(om)
}

func (sb Broker) SendTable(evt hal.Evt, hdr []string, rows [][]string) {
	out := evt.Clone()
	out.Body = hal.Utf8Table(hdr, rows)
	sb.SendAsImage(out)
}

// SendAsImage sends the body of the event as a png file. The png is rendered
// using hal's FixedFont facility.
// This is useful for making sure pre-formatted text stays legible in
// Slack while we wait for them to figure out a way to render things like
// tables of data consistently.
func (sb Broker) SendAsImage(evt hal.Evt) {
	fd := hal.FixedFont()

	// create a tempfile
	f, err := ioutil.TempFile(os.TempDir(), "hal")
	if err != nil {
		evt.Replyf("Could not create tempfile for image upload: %s", err)
		return
	}
	defer os.Remove(f.Name())

	// check for a color preference
	// need to figure out a way to have a helper around this
	var fg color.Color
	fg = color.Black
	// TODO: prefs --set isn't setting the room, etc. remove the filter for now
	fgprefs := hal.FindPrefs("", "", "", "", "image.fg")
	ufgprefs := fgprefs.User(evt.UserId)
	if len(ufgprefs) > 0 {
		fg = fd.ParseColor(ufgprefs[0].Value, fg)
	} else if len(fgprefs) > 0 {
		fg = fd.ParseColor(fgprefs[0].Value, fg)
	}

	var bg color.Color
	bg = color.Transparent
	// TODO: ditto from ft
	//bgprefs := hal.FindPrefs("", sb.Name(), evt.RoomId, "", "image.bg")
	bgprefs := hal.FindPrefs("", "", "", "", "image.bg")
	ubgprefs := bgprefs.User(evt.UserId)
	if len(ubgprefs) > 0 {
		bg = fd.ParseColor(ubgprefs[0].Value, fg)
	} else if len(bgprefs) > 0 {
		bg = fd.ParseColor(bgprefs[0].Value, fg)
	}

	// generate the image
	lines := strings.Split(strings.TrimSpace(evt.Body), "\n")
	textimg := fd.StringsToImage(lines, fg)

	// img has a background color, copy textimg onto it
	img := image.NewRGBA(textimg.Bounds())
	draw.Draw(img, img.Bounds(), &image.Uniform{bg}, image.ZP, draw.Src)
	draw.Draw(img, img.Bounds(), textimg, image.ZP, draw.Src)

	// TODO: apply background color

	// write the png data to the temp file
	png.Encode(f, img)
	f.Close()

	// upload the file
	params := slack.FileUploadParameters{
		File:     f.Name(),
		Filename: "text.png",
		Channels: []string{evt.RoomId},
	}
	_, err = sb.Client.UploadFile(params)
	if err != nil {
		evt.Replyf("Could not upload image: %s", err)
	}
}

// checks the cache to see if the room is known to this broker
func (sb Broker) HasRoom(room string) bool {
	if sb.idRegex.MatchString(room) {
		_, exists := sb.i2c[room]
		return exists
	} else {
		_, exists := sb.c2i[room]
		return exists
	}
}

// Stream is an event loop for Slack events & messages from the RTM API.
// Events are copied to a hal.Evt and forwarded to the exchange where they
// can be processed by registered handlers.
func (sb Broker) Stream(out chan *hal.Evt) {
	for {
		select {
		case msg := <-sb.RTM.IncomingEvents:
			switch ev := msg.Data.(type) {
			case *slack.HelloEvent:
				log.Println("brokers/slack ignoring HelloEvent")

			case *slack.ConnectedEvent:
				log.Printf("brokers/slack ignoring ConnectedEvent")

			case *slack.MessageEvent:
				m := msg.Data.(*slack.MessageEvent)
				// slack channels = hal rooms, see hal-9001/hal/event.go
				e := hal.Evt{
					Body:     m.Text,
					Room:     sb.RoomIdToName(m.Channel),
					RoomId:   m.Channel,
					User:     sb.UserIdToName(m.User),
					UserId:   m.User,
					Broker:   sb,
					Time:     slackTime(m.Timestamp),
					Original: m,
				}

				out <- &e

			case *slack.StarAddedEvent:
				sae := msg.Data.(*slack.StarAddedEvent)
				user := sb.UserIdToName(sae.User)

				e := hal.Evt{
					Body:     fmt.Sprintf("%q added a star", user),
					Room:     sb.RoomIdToName(sae.Item.Channel),
					RoomId:   sae.Item.Channel,
					User:     user,
					UserId:   sae.User,
					Broker:   sb,
					Time:     slackTime(sae.EventTimestamp),
					Original: sae,
				}

				out <- &e

			case *slack.StarRemovedEvent:
				sre := msg.Data.(*slack.StarRemovedEvent)
				user := sb.UserIdToName(sre.User)

				e := hal.Evt{
					Body:     fmt.Sprintf("%q removed a star", user),
					Room:     sb.RoomIdToName(sre.Item.Channel),
					RoomId:   sre.Item.Channel,
					User:     user,
					UserId:   sre.User,
					Broker:   sb,
					Time:     slackTime(sre.EventTimestamp),
					Original: sre,
				}

				out <- &e

			case *slack.ReactionAddedEvent:
				rae := msg.Data.(*slack.ReactionAddedEvent)
				user := sb.UserIdToName(rae.User)

				e := hal.Evt{
					Body:     fmt.Sprintf("%q added reaction %q", user, rae.Reaction),
					Room:     sb.RoomIdToName(rae.Item.Channel),
					RoomId:   rae.Item.Channel,
					User:     user,
					UserId:   rae.User,
					Broker:   sb,
					Time:     slackTime(rae.EventTimestamp),
					Original: rae,
				}

				out <- &e

			case *slack.ReactionRemovedEvent:
				rre := msg.Data.(*slack.ReactionRemovedEvent)
				user := sb.UserIdToName(rre.User)

				e := hal.Evt{
					Body:     fmt.Sprintf("%q removed reaction %q", user, rre.Reaction),
					Room:     sb.RoomIdToName(rre.Item.Channel),
					RoomId:   rre.Item.Channel,
					User:     user,
					UserId:   rre.User,
					Broker:   sb,
					Time:     slackTime(rre.EventTimestamp),
					Original: rre,
				}

				out <- &e

			case *slack.PresenceChangeEvent:
				// ignored

			case *slack.LatencyReport:
				// ignored

			case *slack.RTMError:
				log.Printf("brokers/slack ignoring RTMError: %s\n", ev.Error())

			case *slack.InvalidAuthEvent:
				log.Println("brokers/slack InvalidAuthEvent")
				break

			default:
				log.Printf("brokers/slack: unexpected message: %+v\n", msg)
			}
		}
	}
}

// slackTime converts the timestamp string to time.Time
func slackTime(t string) time.Time {
	if t == "" {
		return time.Now()
	}

	// Slack advises not to parse the timestamp as a float.
	// I tried it. Turns out that string mangling is more accurate than
	// float conversions.
	parts := strings.SplitN(t, ".", 2)

	s, _ := strconv.ParseInt(parts[0], 10, 64)
	ns, _ := strconv.ParseInt(parts[1], 10, 64)

	return time.Unix(s, ns)
}

func (sb *Broker) FillUserCache() {
	users, err := sb.Client.GetUsers()
	if err != nil {
		log.Printf("brokers/slack failed to fetch user list: %s", err)
		return
	}

	for _, user := range users {
		sb.u2i[user.Name] = user.ID
		sb.i2u[user.ID] = user.Name
	}
}

func (sb *Broker) FillRoomCache() {
	rooms, err := sb.Client.GetChannels(true)
	if err != nil {
		log.Printf("brokers/slack failed to fetch room list: %s", err)
		return
	}

	for _, room := range rooms {
		sb.c2i[room.Name] = room.ID
		sb.i2c[room.ID] = room.Name
	}
}

// UserIdToName gets the human-readable username for a user ID using an
// in-memory cache that falls through to the Slack API
func (sb Broker) UserIdToName(id string) string {
	if id == "" {
		log.Println("broker/slack/UserIdToName(): Cannot look up empty string!")
		return ""
	}

	if name, exists := sb.i2u[id]; exists {
		return name
	} else {
		user, err := sb.Client.GetUserInfo(id)
		if err != nil {
			log.Printf("brokers/slack could not retrieve user info for '%s' via API: %s\n", id, err)
			return ""
		}

		// TODO: verify if room/user names are enforced unique in slack or if this is madness
		// remove this if it proves unnecessary (tobert/2016-03-02)
		if _, exists := sb.u2i[user.Name]; exists {
			if sb.u2i[user.Name] != user.ID {
				log.Fatalf("BUG(brokers/slack): found a non-unique user name:ID pair. Had: %q/%q. Got: %q/%q",
					user.Name, sb.u2i[user.Name], user.Name, user.ID)
			}
		}

		sb.i2u[user.ID] = user.Name
		sb.i2u[user.Name] = user.ID

		return user.Name
	}
}

// RoomIdToName gets the human-readable room name for a user ID using an
// in-memory cache that falls through to the Slack API
func (sb Broker) RoomIdToName(id string) string {
	if id == "" {
		log.Println("broker/slack/RoomIdToName(): Cannot look up empty string!")
		return ""
	}

	if name, exists := sb.i2c[id]; exists {
		return name
	} else {
		var name string

		// private channels are on a different endpoint
		if strings.HasPrefix(id, "G") {
			grp, err := sb.Client.GetGroupInfo(id)
			if err != nil {
				log.Printf("brokers/slack could not retrieve group info for '%s' via API: %s\n", id, err)
				return ""
			}
			name = grp.Name
		} else {
			room, err := sb.Client.GetChannelInfo(id)
			if err != nil {
				log.Printf("brokers/slack could not retrieve room info for '%s' via API: %s\n", id, err)
				return ""
			}
			name = room.Name
		}

		// TODO: verify if room/user names are enforced unique in slack or if this is madness
		// remove this if it proves unnecessary (tobert/2016-03-02)
		if _, exists := sb.c2i[name]; exists {
			if sb.c2i[name] != id {
				log.Fatalf("BUG(brokers/slack): found a non-unique room name:ID pair. Had: %q/%q. Got: %q/%q",
					name, sb.c2i[name], name, id)
			}
		}

		sb.i2c[id] = name
		sb.c2i[name] = id

		return name
	}
}

// UserNameToId gets the human-readable username for a user ID using an
// in-memory cache that falls through to the Slack API
func (sb Broker) UserNameToId(name string) string {
	if name == "" {
		log.Println("broker/slack/UserNameToId(): Cannot look up empty string!")
		return ""
	}

	if id, exists := sb.u2i[name]; exists {
		return id
	} else {
		// there doesn't seem to be a name->id lookup so refresh the cache
		// and try again if we get here
		sb.FillUserCache()
		if id, exists := sb.u2i[name]; exists {
			return id
		}

		log.Printf("brokers/slack service does not seem to have knowledge of username %q", name)
		return ""
	}
}

// RoomNameToId gets the human-readable room name for a user ID using an
// in-memory cache that falls through to the Slack API
func (sb Broker) RoomNameToId(name string) string {
	if name == "" {
		log.Println("broker/slack/RoomNameToId(): Cannot look up empty string!")
		return ""
	}

	if id, exists := sb.c2i[name]; exists {
		return id
	} else {
		sb.FillRoomCache()
		if id, exists := sb.c2i[name]; exists {
			return id
		}

		log.Printf("brokers/slack service does not seem to have knowledge of room name %q", name)
		return ""
	}
}
<|endoftext|>


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

package bufpipe_test

import "io"
import "fmt"
import "time"
import "sync"
import "math/rand"
import "bitbucket.org/rawr/golib/bufpipe"

func randomChars(cnt int, rand *rand.Rand) string {
	data := make([]byte, cnt)
	for idx := range data {
		char := byte(rand.Intn(10 + 26 + 26))
		if char < 10 {
			data[idx] = '0' + char
		} else if char < 10+26 {
			data[idx] = 'A' + char - 10
		} else {
			data[idx] = 'a' + char - 36
		}
	}
	return string(data)
}

// In LineMono mode, the consumer cannot see the written data until the pipe is
// closed. Thus, it is possible for the producer to go back to the front of the
// pipe and record the total number of bytes written out. This functionality is
// useful in cases where a file format's header contains information that is
// dependent on what is eventually written.
func Example_lineMono() {
	// The buffer is small enough such that the producer does hit the limit.
	buffer := bufpipe.NewBufferPipe(make([]byte, 256), bufpipe.LineMono)

	rand := rand.New(rand.NewSource(0))
	group := new(sync.WaitGroup)
	group.Add(2)

	// Producer routine.
	go func() {
		defer group.Done()
		defer buffer.Close()

		// In LineMono mode only, it is safe to store a reference to written
		// data and modify later.
		header, _, err := buffer.WriteSlices()
		if err != nil {
			panic(err)
		}

		totalCnt, _ := buffer.Write([]byte("#### "))
		for idx := 0; idx < 8; idx++ {
			data := randomChars(rand.Intn(64), rand) + "\n"

			// So long as the amount of data written has not exceeded the size
			// of the buffer, Write will never fail.
			cnt, err := buffer.Write([]byte(data))
			totalCnt += cnt
			if err == io.ErrShortWrite {
				break
			}

			time.Sleep(100 * time.Millisecond)
		}

		// Write the header afterwards
		copy(header[:4], fmt.Sprintf("%04d", totalCnt))
	}()

	// Consumer routine.
	go func() {
		defer group.Done()

		// In LineMono mode only, a call to ReadSlices is guaranteed to block
		// until the channel is closed. All written data will be made available.
		data, _, _ := buffer.ReadSlices()
		buffer.ReadMark(len(data)) // Technically, this is optional

		fmt.Println(string(data))
	}()

	group.Wait()

	// Output:
	// 0256 kdUhQzHYs2LjaukXEC292UgLOCAPQTCNAKfc0XMNCUuJbsqiHmm6GJMFck
	// whxMYR1k
	// zhMYzktxIv10mIPqBCCwm646E6chwIFZfpX0fjqMu0YKLDhfIMnDq8w9J
	// fQhkT1qEkJfEI0jtbDnIrEXx6G4xMgXEB6auAyBUjPk2jMSgCMVZf8L1VgJemin
	// 2Quy1C5aA00KbYqawNeuXYTvgeUXGu3zyjMUoEIrOx7
	// ecE4dY3ZaTrX03xBY
}

// In LineDual mode, the consumer sees produced data immediately as it becomes
// available. The producer is only allowed to write as much data as the size of
// the underlying buffer. The amount that can be written is independent of the
// operation of the consumer.
func Example_lineDual() {
	// The buffer is small enough such that the producer does hit the limit.
	buffer := bufpipe.NewBufferPipe(make([]byte, 256), bufpipe.LineDual)

	rand := rand.New(rand.NewSource(0))
	group := new(sync.WaitGroup)
	group.Add(2)

	// Producer routine.
	go func() {
		defer group.Done()
		defer buffer.Close()

		buffer.Write([]byte("#### ")) // Write a fake header
		for idx := 0; idx < 8; idx++ {
			data := randomChars(rand.Intn(64), rand) + "\n"

			// So long as the amount of data written has not exceeded the size
			// of the buffer, Write will never fail.
			if _, err := buffer.Write([]byte(data)); err == io.ErrShortWrite {
				break
			}

			time.Sleep(100 * time.Millisecond)
		}
	}()

	// Consumer routine.
	go func() {
		defer group.Done()
		for {
			// Reading can be also done using ReadSlices and ReadMark pairs.
			data, _, err := buffer.ReadSlices()
			if err == io.EOF {
				break
			} else if err != nil {
				panic(err)
			}
			buffer.ReadMark(len(data))
			fmt.Print(string(data))
		}
		fmt.Println()
	}()

	group.Wait()

	// Output:
	// #### kdUhQzHYs2LjaukXEC292UgLOCAPQTCNAKfc0XMNCUuJbsqiHmm6GJMFck
	// whxMYR1k
	// zhMYzktxIv10mIPqBCCwm646E6chwIFZfpX0fjqMu0YKLDhfIMnDq8w9J
	// fQhkT1qEkJfEI0jtbDnIrEXx6G4xMgXEB6auAyBUjPk2jMSgCMVZf8L1VgJemin
	// 2Quy1C5aA00KbYqawNeuXYTvgeUXGu3zyjMUoEIrOx7
	// ecE4dY3ZaTrX03xBY
}

// In RingBlock mode, the consumer sees produced data immediately as it becomes
// available. The producer is allowed to write as much data as it wants so long
// as the consumer continues to read the data in the pipe.
func Example_ringBlock() {
	// Intentionally small buffer to show that data written into the buffer
	// can exceed the size of the buffer itself.
	buffer := bufpipe.NewBufferPipe(make([]byte, 64), bufpipe.RingBlock)

	rand := rand.New(rand.NewSource(0))
	group := new(sync.WaitGroup)
	group.Add(2)

	// Producer routine.
	go func() {
		defer group.Done()
		defer buffer.Close()

		buffer.Write([]byte("#### ")) // Write a fake header
		for idx := 0; idx < 8; idx++ {
			data := randomChars(rand.Intn(64), rand) + "\n"

			// So long as the amount of data written has not exceeded the size
			// of the buffer, Write will never fail.
			buffer.Write([]byte(data))

			time.Sleep(100 * time.Millisecond)
		}
	}()

	// Consumer routine.
	go func() {
		defer group.Done()

		data := make([]byte, 64)
		for {
			// Reading can also be done using the Read method.
			cnt, err := buffer.Read(data)
			fmt.Print(string(data[:cnt]))
			if err == io.EOF {
				break
			}
		}
		fmt.Println()
	}()

	group.Wait()

	// Output:
	// #### kdUhQzHYs2LjaukXEC292UgLOCAPQTCNAKfc0XMNCUuJbsqiHmm6GJMFck
	// whxMYR1k
	// zhMYzktxIv10mIPqBCCwm646E6chwIFZfpX0fjqMu0YKLDhfIMnDq8w9J
	// fQhkT1qEkJfEI0jtbDnIrEXx6G4xMgXEB6auAyBUjPk2jMSgCMVZf8L1VgJemin
	// 2Quy1C5aA00KbYqawNeuXYTvgeUXGu3zyjMUoEIrOx7
	// ecE4dY3ZaTrX03xBYJ04OzomME36yth76CFmg2zTolzKhYByvZ8
	// FQMuYbcWHLcUu4yL3aBZkwJrbDFUcHpGnBGfbDq4aFlLS5vGOm6mYOjHZll
	// iP0QQKpKp3cz
}
<commit_msg>Try and fix example code for bufpipe<commit_after>// Copyright 2014, Joe Tsai. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.

package bufpipe_test

import "io"
import "fmt"
import "time"
import "sync"
import "math/rand"
import "bitbucket.org/rawr/golib/bufpipe"

func randomChars(cnt int, rand *rand.Rand) string {
	data := make([]byte, cnt)
	for idx := range data {
		char := byte(rand.Intn(10 + 26 + 26))
		if char < 10 {
			data[idx] = '0' + char
		} else if char < 10+26 {
			data[idx] = 'A' + char - 10
		} else {
			data[idx] = 'a' + char - 36
		}
	}
	return string(data)
}

// In LineMono mode, the consumer cannot see the written data until the pipe is
// closed. Thus, it is possible for the producer to go back to the front of the
// pipe and record the total number of bytes written out. This functionality is
// useful in cases where a file format's header contains information that is
// dependent on what is eventually written.
func ExampleBufferPipe_lineMono() {
	// The buffer is small enough such that the producer does hit the limit.
	buffer := bufpipe.NewBufferPipe(make([]byte, 256), bufpipe.LineMono)

	rand := rand.New(rand.NewSource(0))
	group := new(sync.WaitGroup)
	group.Add(2)

	// Producer routine.
	go func() {
		defer group.Done()
		defer buffer.Close()

		// In LineMono mode only, it is safe to store a reference to written
		// data and modify later.
		header, _, err := buffer.WriteSlices()
		if err != nil {
			panic(err)
		}

		totalCnt, _ := buffer.Write([]byte("#### "))
		for idx := 0; idx < 8; idx++ {
			data := randomChars(rand.Intn(64), rand) + "\n"

			// So long as the amount of data written has not exceeded the size
			// of the buffer, Write will never fail.
			cnt, err := buffer.Write([]byte(data))
			totalCnt += cnt
			if err == io.ErrShortWrite {
				break
			}

			time.Sleep(100 * time.Millisecond)
		}

		// Write the header afterwards
		copy(header[:4], fmt.Sprintf("%04d", totalCnt))
	}()

	// Consumer routine.
	go func() {
		defer group.Done()

		// In LineMono mode only, a call to ReadSlices is guaranteed to block
		// until the channel is closed. All written data will be made available.
		data, _, _ := buffer.ReadSlices()
		buffer.ReadMark(len(data)) // Technically, this is optional

		fmt.Println(string(data))
	}()

	group.Wait()

	// Output:
	// 0256 kdUhQzHYs2LjaukXEC292UgLOCAPQTCNAKfc0XMNCUuJbsqiHmm6GJMFck
	// whxMYR1k
	// zhMYzktxIv10mIPqBCCwm646E6chwIFZfpX0fjqMu0YKLDhfIMnDq8w9J
	// fQhkT1qEkJfEI0jtbDnIrEXx6G4xMgXEB6auAyBUjPk2jMSgCMVZf8L1VgJemin
	// 2Quy1C5aA00KbYqawNeuXYTvgeUXGu3zyjMUoEIrOx7
	// ecE4dY3ZaTrX03xBY
}

// In LineDual mode, the consumer sees produced data immediately as it becomes
// available. The producer is only allowed to write as much data as the size of
// the underlying buffer. The amount that can be written is independent of the
// operation of the consumer.
func ExampleBufferPipe_lineDual() {
	// The buffer is small enough such that the producer does hit the limit.
	buffer := bufpipe.NewBufferPipe(make([]byte, 256), bufpipe.LineDual)

	rand := rand.New(rand.NewSource(0))
	group := new(sync.WaitGroup)
	group.Add(2)

	// Producer routine.
	go func() {
		defer group.Done()
		defer buffer.Close()

		buffer.Write([]byte("#### ")) // Write a fake header
		for idx := 0; idx < 8; idx++ {
			data := randomChars(rand.Intn(64), rand) + "\n"

			// So long as the amount of data written has not exceeded the size
			// of the buffer, Write will never fail.
			if _, err := buffer.Write([]byte(data)); err == io.ErrShortWrite {
				break
			}

			time.Sleep(100 * time.Millisecond)
		}
	}()

	// Consumer routine.
	go func() {
		defer group.Done()
		for {
			// Reading can be also done using ReadSlices and ReadMark pairs.
			data, _, err := buffer.ReadSlices()
			if err == io.EOF {
				break
			} else if err != nil {
				panic(err)
			}
			buffer.ReadMark(len(data))
			fmt.Print(string(data))
		}
		fmt.Println()
	}()

	group.Wait()

	// Output:
	// #### kdUhQzHYs2LjaukXEC292UgLOCAPQTCNAKfc0XMNCUuJbsqiHmm6GJMFck
	// whxMYR1k
	// zhMYzktxIv10mIPqBCCwm646E6chwIFZfpX0fjqMu0YKLDhfIMnDq8w9J
	// fQhkT1qEkJfEI0jtbDnIrEXx6G4xMgXEB6auAyBUjPk2jMSgCMVZf8L1VgJemin
	// 2Quy1C5aA00KbYqawNeuXYTvgeUXGu3zyjMUoEIrOx7
	// ecE4dY3ZaTrX03xBY
}

// In RingBlock mode, the consumer sees produced data immediately as it becomes
// available. The producer is allowed to write as much data as it wants so long
// as the consumer continues to read the data in the pipe.
func ExampleBufferPipe_ringBlock() {
	// Intentionally small buffer to show that data written into the buffer
	// can exceed the size of the buffer itself.
	buffer := bufpipe.NewBufferPipe(make([]byte, 64), bufpipe.RingBlock)

	rand := rand.New(rand.NewSource(0))
	group := new(sync.WaitGroup)
	group.Add(2)

	// Producer routine.
	go func() {
		defer group.Done()
		defer buffer.Close()

		buffer.Write([]byte("#### ")) // Write a fake header
		for idx := 0; idx < 8; idx++ {
			data := randomChars(rand.Intn(64), rand) + "\n"

			// So long as the amount of data written has not exceeded the size
			// of the buffer, Write will never fail.
			buffer.Write([]byte(data))

			time.Sleep(100 * time.Millisecond)
		}
	}()

	// Consumer routine.
	go func() {
		defer group.Done()

		data := make([]byte, 64)
		for {
			// Reading can also be done using the Read method.
			cnt, err := buffer.Read(data)
			fmt.Print(string(data[:cnt]))
			if err == io.EOF {
				break
			}
		}
		fmt.Println()
	}()

	group.Wait()

	// Output:
	// #### kdUhQzHYs2LjaukXEC292UgLOCAPQTCNAKfc0XMNCUuJbsqiHmm6GJMFck
	// whxMYR1k
	// zhMYzktxIv10mIPqBCCwm646E6chwIFZfpX0fjqMu0YKLDhfIMnDq8w9J
	// fQhkT1qEkJfEI0jtbDnIrEXx6G4xMgXEB6auAyBUjPk2jMSgCMVZf8L1VgJemin
	// 2Quy1C5aA00KbYqawNeuXYTvgeUXGu3zyjMUoEIrOx7
	// ecE4dY3ZaTrX03xBYJ04OzomME36yth76CFmg2zTolzKhYByvZ8
	// FQMuYbcWHLcUu4yL3aBZkwJrbDFUcHpGnBGfbDq4aFlLS5vGOm6mYOjHZll
	// iP0QQKpKp3cz
}
<|endoftext|>


<commit_before>package provider_fm

import (
	"fmt"
	"testing"

	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)

func TestAccResourceIntegerBasic(t *testing.T) {
	t.Parallel()
	resource.UnitTest(t, resource.TestCase{
		PreCheck:                 func() { testAccPreCheck(t) },
		ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(),
		Steps: []resource.TestStep{
			{
				Config: testRandomIntegerBasic,
				Check: resource.ComposeTestCheckFunc(
					testAccResourceIntegerBasic("random_integer.integer_1"),
				),
			},
			{
				ResourceName:      "random_integer.integer_1",
				ImportState:       true,
				ImportStateId:     "3,1,3,12345",
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccResourceIntegerUpdate(t *testing.T) {
	t.Parallel()
	resource.UnitTest(t, resource.TestCase{
		PreCheck:                 func() { testAccPreCheck(t) },
		ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(),
		Steps: []resource.TestStep{
			{
				Config: testRandomIntegerBasic,
				Check: resource.ComposeTestCheckFunc(
					testAccResourceIntegerBasic("random_integer.integer_1"),
				),
			},
			{
				Config: testRandomIntegerUpdate,
				Check: resource.ComposeTestCheckFunc(
					testAccResourceIntegerUpdate("random_integer.integer_1"),
				),
			},
		},
	})
}

func TestAccResourceIntegerSeedless_to_seeded(t *testing.T) {
	t.Parallel()
	resource.UnitTest(t, resource.TestCase{
		PreCheck:                 func() { testAccPreCheck(t) },
		ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(),
		Steps: []resource.TestStep{
			{
				Config: testRandomIntegerSeedless,
				Check: resource.ComposeTestCheckFunc(
					testAccResourceIntegerSeedless("random_integer.integer_1"),
				),
			},
			{
				Config: testRandomIntegerUpdate,
				Check: resource.ComposeTestCheckFunc(
					testAccResourceIntegerUpdate("random_integer.integer_1"),
				),
			},
		},
	})
}

func TestAccResourceIntegerSeeded_to_seedless(t *testing.T) {
	t.Parallel()
	resource.UnitTest(t, resource.TestCase{
		PreCheck:                 func() { testAccPreCheck(t) },
		ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(),
		Steps: []resource.TestStep{
			{
				Config: testRandomIntegerBasic,
				Check: resource.ComposeTestCheckFunc(
					testAccResourceIntegerBasic("random_integer.integer_1"),
				),
			},
			{
				Config: testRandomIntegerSeedless,
				Check: resource.ComposeTestCheckFunc(
					testAccResourceIntegerSeedless("random_integer.integer_1"),
				),
			},
		},
	})
}

func testAccResourceIntegerBasic(id string) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[id]
		if !ok {
			return fmt.Errorf("Not found: %s", id)
		}
		result := rs.Primary.Attributes["result"]

		if rs.Primary.ID == "" {
			return fmt.Errorf("No ID is set")
		}

		if result == "" {
			return fmt.Errorf("Result not found")
		}

		if result != "3" {
			return fmt.Errorf("Invalid result %s. Seed does not result in correct value", result)
		}
		return nil
	}
}

func testAccResourceIntegerUpdate(id string) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[id]
		if !ok {
			return fmt.Errorf("Not found: %s", id)
		}
		result := rs.Primary.Attributes["result"]

		if rs.Primary.ID == "" {
			return fmt.Errorf("No ID is set")
		}

		if result == "" {
			return fmt.Errorf("Result not found")
		}

		if result != "2" {
			return fmt.Errorf("Invalid result %s. Seed does not result in correct value", result)
		}
		return nil
	}
}

// testAccResourceIntegerSeedless only checks that some result was returned, and does not validate the value.
func testAccResourceIntegerSeedless(id string) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[id]
		if !ok {
			return fmt.Errorf("Not found: %s", id)
		}
		result := rs.Primary.Attributes["result"]

		if rs.Primary.ID == "" {
			return fmt.Errorf("No ID is set")
		}

		if result == "" {
			return fmt.Errorf("Result not found")
		}

		return nil
	}
}

const (
	testRandomIntegerBasic = `
resource "random_integer" "integer_1" {
   min  = 1
   max  = 3
   seed = "12345"
}
`

	testRandomIntegerUpdate = `
resource "random_integer" "integer_1" {
   min  = 1
   max  = 3
   seed = "123456"
}
`

	testRandomIntegerSeedless = `
resource "random_integer" "integer_1" {
   min  = 1
   max  = 3
}
`
)
<commit_msg>Reinstating failing test (#177)<commit_after>package provider_fm

import (
	"fmt"
	"testing"

	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)

func TestAccResourceIntegerBasic(t *testing.T) {
	t.Parallel()
	resource.UnitTest(t, resource.TestCase{
		PreCheck:                 func() { testAccPreCheck(t) },
		ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(),
		Steps: []resource.TestStep{
			{
				Config: testRandomIntegerBasic,
				Check: resource.ComposeTestCheckFunc(
					testAccResourceIntegerBasic("random_integer.integer_1"),
				),
			},
			{
				ResourceName:      "random_integer.integer_1",
				ImportState:       true,
				ImportStateId:     "3,1,3,12345",
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccResourceIntegerUpdate(t *testing.T) {
	t.Parallel()
	resource.UnitTest(t, resource.TestCase{
		PreCheck:                 func() { testAccPreCheck(t) },
		ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(),
		Steps: []resource.TestStep{
			{
				Config: testRandomIntegerBasic,
				Check: resource.ComposeTestCheckFunc(
					testAccResourceIntegerBasic("random_integer.integer_1"),
				),
			},
			{
				Config: testRandomIntegerUpdate,
				Check: resource.ComposeTestCheckFunc(
					testAccResourceIntegerUpdate("random_integer.integer_1"),
				),
			},
		},
	})
}

func TestAccResourceIntegerSeedless_to_seeded(t *testing.T) {
	t.Parallel()
	resource.UnitTest(t, resource.TestCase{
		PreCheck:                 func() { testAccPreCheck(t) },
		ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(),
		Steps: []resource.TestStep{
			{
				Config: testRandomIntegerSeedless,
				Check: resource.ComposeTestCheckFunc(
					testAccResourceIntegerSeedless("random_integer.integer_1"),
				),
			},
			{
				Config: testRandomIntegerUpdate,
				Check: resource.ComposeTestCheckFunc(
					testAccResourceIntegerUpdate("random_integer.integer_1"),
				),
			},
		},
	})
}

func TestAccResourceIntegerSeeded_to_seedless(t *testing.T) {
	t.Parallel()
	resource.UnitTest(t, resource.TestCase{
		PreCheck:                 func() { testAccPreCheck(t) },
		ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(),
		Steps: []resource.TestStep{
			{
				Config: testRandomIntegerBasic,
				Check: resource.ComposeTestCheckFunc(
					testAccResourceIntegerBasic("random_integer.integer_1"),
				),
			},
			{
				Config: testRandomIntegerSeedless,
				Check: resource.ComposeTestCheckFunc(
					testAccResourceIntegerSeedless("random_integer.integer_1"),
				),
			},
		},
	})
}

func TestAccResourceIntegerBig(t *testing.T) {
	t.Parallel()
	resource.UnitTest(t, resource.TestCase{
		PreCheck:                 func() { testAccPreCheck(t) },
		ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(),
		Steps: []resource.TestStep{
			{
				Config: testRandomIntegerBig,
			},
			{
				ResourceName:      "random_integer.integer_1",
				ImportState:       true,
				ImportStateId:     "7227701560655103598,7227701560655103597,7227701560655103598,12345",
				ImportStateVerify: true,
			},
		},
	})
}

func testAccResourceIntegerBasic(id string) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[id]
		if !ok {
			return fmt.Errorf("Not found: %s", id)
		}
		result := rs.Primary.Attributes["result"]

		if rs.Primary.ID == "" {
			return fmt.Errorf("No ID is set")
		}

		if result == "" {
			return fmt.Errorf("Result not found")
		}

		if result != "3" {
			return fmt.Errorf("Invalid result %s. Seed does not result in correct value", result)
		}
		return nil
	}
}

func testAccResourceIntegerUpdate(id string) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[id]
		if !ok {
			return fmt.Errorf("Not found: %s", id)
		}
		result := rs.Primary.Attributes["result"]

		if rs.Primary.ID == "" {
			return fmt.Errorf("No ID is set")
		}

		if result == "" {
			return fmt.Errorf("Result not found")
		}

		if result != "2" {
			return fmt.Errorf("Invalid result %s. Seed does not result in correct value", result)
		}
		return nil
	}
}

// testAccResourceIntegerSeedless only checks that some result was returned, and does not validate the value.
func testAccResourceIntegerSeedless(id string) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[id]
		if !ok {
			return fmt.Errorf("Not found: %s", id)
		}
		result := rs.Primary.Attributes["result"]

		if rs.Primary.ID == "" {
			return fmt.Errorf("No ID is set")
		}

		if result == "" {
			return fmt.Errorf("Result not found")
		}

		return nil
	}
}

const (
	testRandomIntegerBasic = `
resource "random_integer" "integer_1" {
   min  = 1
   max  = 3
   seed = "12345"
}
`

	testRandomIntegerUpdate = `
resource "random_integer" "integer_1" {
   min  = 1
   max  = 3
   seed = "123456"
}
`

	testRandomIntegerSeedless = `
resource "random_integer" "integer_1" {
   min  = 1
   max  = 3
}
`

	testRandomIntegerBig = `
resource "random_integer" "integer_1" {
   max  = 7227701560655103598
   min  = 7227701560655103597
   seed = 12345
}`
)
<|endoftext|>


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

package xdgbasedir

import (
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"testing"

	"github.com/zchee/go-xdgbasedir/home"
)

func TestDataHome(t *testing.T) {
	defaultDataHome := filepath.Join(home.Dir(), ".local", "share")

	tests := []struct {
		name string
		env  string
		want string
	}{
		{
			name: "Set env based specification",
			env:  defaultDataHome,
			want: defaultDataHome,
		},
		{
			name: "Set env based different from specification",
			env:  filepath.Join(string(filepath.Separator), "tmp", "xdg", ".local", "share"),
			want: filepath.Join(string(filepath.Separator), "tmp", "xdg", ".local", "share"),
		},
		{
			name: "Empty env",
			env:  "",
			want: defaultDataHome,
		},
	}
	for _, tt := range tests {
		os.Unsetenv("XDG_DATA_HOME")
		if tt.env != "" {
			os.Setenv("XDG_DATA_HOME", tt.env)
		}
		t.Run(tt.name, func(t *testing.T) {
			if got := DataHome(); got != tt.want {
				t.Errorf("DataHome() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestConfigHome(t *testing.T) {
	defaultConfigHome := filepath.Join(home.Dir(), ".config")

	tests := []struct {
		name string
		env  string
		want string
	}{
		{
			name: "Set env based specification",
			env:  defaultConfigHome,
			want: defaultConfigHome,
		},
		{
			name: "Set env based different from specification",
			env:  filepath.Join(string(filepath.Separator), "tmp", "config"),
			want: filepath.Join(string(filepath.Separator), "tmp", "config"),
		},
		{
			name: "Empty env",
			env:  "",
			want: defaultConfigHome,
		},
	}
	for _, tt := range tests {
		os.Unsetenv("XDG_CONFIG_HOME")
		if tt.env != "" {
			os.Setenv("XDG_CONFIG_HOME", tt.env)
		} else {
			if runtime.GOOS == "darwin" {
				tt.want = filepath.Join(home.Dir(), ".config")
			}
		}
		t.Run(tt.name, func(t *testing.T) {
			if got := ConfigHome(); got != tt.want {
				t.Errorf("ConfigHome() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestDataDirs(t *testing.T) {
	defaultDataDirs := filepath.Join(string(filepath.Separator), "usr", "local", "share", string(filepath.ListSeparator), "usr", "share")

	tests := []struct {
		name string
		env  string
		want string
	}{
		{
			name: "Set env based specification",
			env:  defaultDataDirs,
			want: defaultDataDirs,
		},
		{
			name: "Set env based different from specification",
			env:  filepath.Join(string(filepath.Separator), "opt", "local", "share"),
			want: filepath.Join(string(filepath.Separator), "opt", "local", "share"),
		},
		{
			name: "Empty env",
			env:  "",
			want: defaultDataDirs,
		},
	}
	for _, tt := range tests {
		os.Unsetenv("XDG_DATA_DIRS")
		if tt.env != "" {
			os.Setenv("XDG_DATA_DIRS", tt.env)
		}
		t.Run(tt.name, func(t *testing.T) {
			if got := DataDirs(); got != tt.want {
				t.Errorf("DataDirs() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestConfigDirs(t *testing.T) {
	defaultConfigDirs := filepath.Join(string(filepath.Separator), "etc", "xdg")

	tests := []struct {
		name string
		env  string
		want string
	}{
		{
			name: "Set env based specification",
			env:  defaultConfigDirs,
			want: defaultConfigDirs,
		},
		{
			name: "Set env based different from specification",
			env:  filepath.Join(string(filepath.Separator), "var", "etc", "xdg"),
			want: filepath.Join(string(filepath.Separator), "var", "etc", "xdg"),
		},
		{
			name: "Empty env",
			env:  "",
			want: defaultConfigDirs,
		},
	}
	for _, tt := range tests {
		os.Unsetenv("XDG_CONFIG_DIRS")
		if tt.env != "" {
			os.Setenv("XDG_CONFIG_DIRS", tt.env)
		}
		t.Run(tt.name, func(t *testing.T) {
			if got := ConfigDirs(); got != tt.want {
				t.Errorf("ConfigDirs() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestCacheHome(t *testing.T) {
	var defaultCacheHome string
	switch runtime.GOOS {
	case "darwin":
		defaultCacheHome = filepath.Join(home.Dir(), "Library", "Caches")
	default:
		defaultCacheHome = filepath.Join(home.Dir(), ".cache")
	}

	tests := []struct {
		name string
		env  string
		want string
	}{
		{
			name: "Set env based specification",
			env:  defaultCacheHome,
			want: defaultCacheHome,
		},
		{
			name: "Set env based different from specification",
			env:  filepath.Join(string(filepath.Separator), "tmp", "cache"),
			want: filepath.Join(string(filepath.Separator), "tmp", "cache"),
		},
		{
			name: "Empty env",
			env:  "",
			want: defaultCacheHome,
		},
	}
	for _, tt := range tests {
		os.Unsetenv("XDG_CACHE_HOME")
		if tt.env != "" {
			os.Setenv("XDG_CACHE_HOME", tt.env)
		}
		t.Run(tt.name, func(t *testing.T) {
			if got := CacheHome(); got != tt.want {
				t.Errorf("CacheHome() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestRuntimeDir(t *testing.T) {
	defaultRuntimeDir := filepath.Join(string(filepath.Separator), "run", "user", strconv.Itoa(os.Getuid()))

	tests := []struct {
		name string
		env  string
		want string
	}{
		{
			name: "Set env based specification",
			env:  defaultRuntimeDir,
			want: defaultRuntimeDir,
		},
		{
			name: "Set env based different from specification",
			env:  filepath.Join(string(filepath.Separator), "tmp", "user", "1000"),
			want: filepath.Join(string(filepath.Separator), "tmp", "user", "1000"),
		},
		{
			name: "Empty env",
			env:  "",
			want: defaultRuntimeDir,
		},
	}
	for _, tt := range tests {
		os.Unsetenv("XDG_RUNTIME_DIR")
		if tt.env != "" {
			os.Setenv("XDG_RUNTIME_DIR", tt.env)
		}
		t.Run(tt.name, func(t *testing.T) {
			if got := RuntimeDir(); got != tt.want {
				t.Errorf("RuntimeDir() = %v, want %v", got, tt.want)
			}
		})
	}
}
<commit_msg>xdgbasedir: support os specific testcase<commit_after>// Copyright 2017 The go-xdgbasedir Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package xdgbasedir

import (
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"testing"

	"github.com/zchee/go-xdgbasedir/home"
)

func TestDataHome(t *testing.T) {
	var testDefaultDataHome string
	switch runtime.GOOS {
	case "windows":
		testDefaultDataHome = filepath.Join(home.Dir(), "AppData", "Local")
	default:
		testDefaultDataHome = filepath.Join(home.Dir(), ".local", "share")
	}

	tests := []struct {
		name string
		env  string
		want string
		mode mode
	}{
		{
			name: "set env based specification",
			env:  testDefaultDataHome,
			want: testDefaultDataHome,
		},
		{
			name: "set env based different from specification",
			env:  filepath.Join(string(filepath.Separator), "tmp", "xdg", ".local", "share"),
			want: filepath.Join(string(filepath.Separator), "tmp", "xdg", ".local", "share"),
		},
		{
			name: "empty env",
			env:  "",
			want: testDefaultDataHome,
		},
	}
	for _, tt := range tests {
		os.Setenv("XDG_DATA_HOME", tt.env)

		t.Run(tt.name, func(t *testing.T) {
			if got := DataHome(); got != tt.want {
				t.Errorf("DataHome() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestConfigHome(t *testing.T) {
	var testDefaultConfigHome string
	switch runtime.GOOS {
	case "windows":
		testDefaultConfigHome = filepath.Join(home.Dir(), "AppData", "Local")
	default:
		testDefaultConfigHome = filepath.Join(home.Dir(), ".config")
	}

	tests := []struct {
		name string
		env  string
		want string
	}{
		{
			name: "set env based specification",
			env:  testDefaultConfigHome,
			want: testDefaultConfigHome,
		},
		{
			name: "set env based different from specification",
			env:  filepath.Join(string(filepath.Separator), "tmp", "config"),
			want: filepath.Join(string(filepath.Separator), "tmp", "config"),
		},
		{
			name: "empty env",
			env:  "",
			want: testDefaultConfigHome,
		},
	}
	for _, tt := range tests {
		os.Setenv("XDG_CONFIG_HOME", tt.env)

		t.Run(tt.name, func(t *testing.T) {
			if got := ConfigHome(); got != tt.want {
				t.Errorf("ConfigHome() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestDataDirs(t *testing.T) {
	var testDefaultDataDirs string
	switch runtime.GOOS {
	case "windows":
		testDefaultDataDirs = filepath.Join(home.Dir(), "AppData", "Local")
	default:
		testDefaultDataDirs = filepath.Join("/usr", "local", "share") + string(filepath.ListSeparator) + filepath.Join("/usr", "share")
	}

	tests := []struct {
		name string
		env  string
		want string
	}{
		{
			name: "set env based specification",
			env:  testDefaultDataDirs,
			want: testDefaultDataDirs,
		},
		{
			name: "set env based different from specification",
			env:  filepath.Join("/opt", "local", "share"),
			want: filepath.Join("/opt", "local", "share"),
		},
		{
			name: "empty env",
			env:  "",
			want: testDefaultDataDirs,
		},
	}
	for _, tt := range tests {
		os.Setenv("XDG_DATA_DIRS", tt.env)

		t.Run(tt.name, func(t *testing.T) {
			if got := DataDirs(); got != tt.want {
				t.Errorf("DataDirs() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestConfigDirs(t *testing.T) {
	var testDefaultConfigDirs string
	switch runtime.GOOS {
	case "windows":
		testDefaultConfigDirs = filepath.Join(home.Dir(), "AppData", "Local")
	default:
		testDefaultConfigDirs = filepath.Join("/etc", "xdg")
	}

	tests := []struct {
		name string
		env  string
		want string
	}{
		{
			name: "set env based specification",
			env:  testDefaultConfigDirs,
			want: testDefaultConfigDirs,
		},
		{
			name: "set env based different from specification",
			env:  filepath.Join(string(filepath.Separator), "var", "etc", "xdg"),
			want: filepath.Join(string(filepath.Separator), "var", "etc", "xdg"),
		},
		{
			name: "empty env",
			env:  "",
			want: testDefaultConfigDirs,
		},
	}
	for _, tt := range tests {
		os.Unsetenv("XDG_CONFIG_DIRS")
		if tt.env != "" {
			os.Setenv("XDG_CONFIG_DIRS", tt.env)
		}
		t.Run(tt.name, func(t *testing.T) {
			if got := ConfigDirs(); got != tt.want {
				t.Errorf("ConfigDirs() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestCacheHome(t *testing.T) {
	var testDefaultCacheHome string
	switch runtime.GOOS {
	case "windows":
		testDefaultCacheHome = filepath.Join(home.Dir(), "AppData", "Local", "cache")
	default:
		testDefaultCacheHome = filepath.Join(home.Dir(), ".cache")
	}

	tests := []struct {
		name string
		env  string
		want string
	}{
		{
			name: "set env based specification",
			env:  testDefaultCacheHome,
			want: testDefaultCacheHome,
		},
		{
			name: "set env based different from specification",
			env:  filepath.Join(string(filepath.Separator), "tmp", "cache"),
			want: filepath.Join(string(filepath.Separator), "tmp", "cache"),
		},
		{
			name: "empty env",
			env:  "",
			want: testDefaultCacheHome,
		},
	}
	for _, tt := range tests {
		os.Setenv("XDG_CACHE_HOME", tt.env)

		t.Run(tt.name, func(t *testing.T) {
			if got := CacheHome(); got != tt.want {
				t.Errorf("CacheHome() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestRuntimeDir(t *testing.T) {
	var testDefaultRuntimeDir string
	switch runtime.GOOS {
	case "windows":
		testDefaultRuntimeDir = home.Dir()
	default:
		testDefaultRuntimeDir = filepath.Join("/run", "user", strconv.Itoa(os.Getuid()))
	}

	tests := []struct {
		name string
		env  string
		want string
	}{
		{
			name: "set env based specification",
			env:  testDefaultRuntimeDir,
			want: testDefaultRuntimeDir,
		},
		{
			name: "set env based different from specification",
			env:  filepath.Join(string(filepath.Separator), "tmp", "user", "1000"),
			want: filepath.Join(string(filepath.Separator), "tmp", "user", "1000"),
		},
		{
			name: "empty env",
			env:  "",
			want: testDefaultRuntimeDir,
		},
	}
	for _, tt := range tests {
		os.Setenv("XDG_RUNTIME_DIR", tt.env)

		t.Run(tt.name, func(t *testing.T) {
			if got := RuntimeDir(); got != tt.want {
				t.Errorf("RuntimeDir() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestNativeMode(t *testing.T) {
	// skip test if not darwin
	if runtime.GOOS != "darwin" {
		return
	}

	cached = false
	Mode = Native

	tests := []struct {
		name string
		fn   string
		want string
	}{
		{
			name: "DataHome",
			fn:   DataHome(),
			want: filepath.Join(home.Dir(), "Library", "Application Support"),
		},
		{
			name: "ConfigHome",
			fn:   ConfigHome(),
			want: filepath.Join(home.Dir(), "Library", "Preferences"),
		},
		{
			name: "DataDirs",
			fn:   DataDirs(),
			want: filepath.Join(home.Dir(), "Library", "Application Support"),
		},
		{
			name: "ConfigDirs",
			fn:   ConfigDirs(),
			want: filepath.Join(home.Dir(), "Library", "Preferences"),
		},
		{
			name: "CacheHome",
			fn:   CacheHome(),
			want: filepath.Join(home.Dir(), "Library", "Caches"),
		},
		{
			name: "RuntimeDir",
			fn:   RuntimeDir(),
			want: filepath.Join(home.Dir(), "Library", "Application Support"),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := tt.fn; got != tt.want {
				t.Errorf("NativeMode() = %v, want %v", got, tt.want)
			}
		})
	}
}
<|endoftext|>


<commit_before>package rubyapp

import (
	"fmt"
	"log"
	"path/filepath"
	"strings"

	"github.com/hashicorp/otto/app"
	"github.com/hashicorp/otto/appfile"
	rubySP "github.com/hashicorp/otto/builtin/scriptpack/ruby"
	stdSP "github.com/hashicorp/otto/builtin/scriptpack/stdlib"
	"github.com/hashicorp/otto/helper/bindata"
	"github.com/hashicorp/otto/helper/compile"
	"github.com/hashicorp/otto/helper/oneline"
	"github.com/hashicorp/otto/helper/packer"
	"github.com/hashicorp/otto/helper/schema"
	"github.com/hashicorp/otto/helper/terraform"
	"github.com/hashicorp/otto/helper/vagrant"
	"github.com/hashicorp/otto/scriptpack"
)

//go:generate go-bindata -pkg=rubyapp -nomemcopy -nometadata ./data/...

// App is an implementation of app.App
type App struct{}

func (a *App) Meta() (*app.Meta, error) {
	return Meta, nil
}

func (a *App) Implicit(ctx *app.Context) (*appfile.File, error) {
	// depMap is our mapping of gem to dependency URL
	depMap := map[string]string{
		"redis": "github.com/hashicorp/otto/examples/redis",
	}

	// used keeps track of dependencies we've used so we don't
	// double-up on dependencies
	used := map[string]struct{}{}

	// Get the path to the working directory
	dir := filepath.Dir(ctx.Appfile.Path)
	log.Printf("[DEBUG] app: implicit check path: %s", dir)

	// If we have certain gems, add the dependencies
	var deps []*appfile.Dependency
	for k, v := range depMap {
		// If we already used v, then don't do it
		if _, ok := used[v]; ok {
			continue
		}

		// If we don't have the gem, then nothing to do
		log.Printf("[DEBUG] app: checking for Gem: %s", k)
		ok, err := HasGem(dir, k)
		if err != nil {
			return nil, err
		}
		if !ok {
			log.Printf("[DEBUG] app: Gem not found: %s", k)
			continue
		}
		log.Printf("[INFO] app: found Gem '%s', adding dep: %s", k, v)

		// We have it! Add the implicit
		deps = append(deps, &appfile.Dependency{
			Source: v,
		})
		used[v] = struct{}{}
	}

	// Build an implicit Appfile if we have deps
	var result *appfile.File
	if len(deps) > 0 {
		result = &appfile.File{
			Application: &appfile.Application{
				Dependencies: deps,
			},
		}
	}

	return result, nil
}

func (a *App) Compile(ctx *app.Context) (*app.CompileResult, error) {
	var opts compile.AppOptions
	custom := &customizations{Opts: &opts}
	opts = compile.AppOptions{
		Ctx: ctx,
		Result: &app.CompileResult{
			Version: 1,
		},
		Bindata: &bindata.Data{
			Asset:    Asset,
			AssetDir: AssetDir,
			Context:  map[string]interface{}{},
		},
		ScriptPacks: []*scriptpack.ScriptPack{
			&stdSP.ScriptPack,
			&rubySP.ScriptPack,
		},
		Customization: (&compile.Customization{
			Callback: custom.process,
			Schema: map[string]*schema.FieldSchema{
				"ruby_version": &schema.FieldSchema{
					Type:        schema.TypeString,
					Default:     "detect",
					Description: "Ruby version to install",
				},
			},
		}).Merge(compile.VagrantCustomizations(&opts)),
	}

	return compile.App(&opts)
}

func (a *App) Build(ctx *app.Context) error {
	return packer.Build(ctx, &packer.BuildOptions{
		InfraOutputMap: map[string]string{
			"region":        "aws_region",
			"vpc_id":        "aws_vpc_id",
			"subnet_public": "aws_subnet_id",
		},
	})
}

func (a *App) Deploy(ctx *app.Context) error {
	return terraform.Deploy(&terraform.DeployOptions{
		InfraOutputMap: map[string]string{
			"region":         "aws_region",
			"subnet-private": "private_subnet_id",
			"subnet-public":  "public_subnet_id",
		},
	}).Route(ctx)
}

func (a *App) Dev(ctx *app.Context) error {
	var layered *vagrant.Layered

	// We only setup a layered environment if we've recompiled since
	// version 0. If we're still at version 0 then we have to use the
	// non-layered dev environment.
	if ctx.CompileResult.Version > 0 {
		// Read the go version, since we use that for our layer
		version, err := oneline.Read(filepath.Join(ctx.Dir, "dev", "ruby_version"))
		if err != nil {
			return err
		}

		// Setup layers
		layered, err = vagrant.DevLayered(ctx, []*vagrant.Layer{
			&vagrant.Layer{
				ID:          fmt.Sprintf("ruby%s", version),
				Vagrantfile: filepath.Join(ctx.Dir, "dev", "layer-base", "Vagrantfile"),
			},
		})
		if err != nil {
			return err
		}
	}

	// Build the actual development environment
	return vagrant.Dev(&vagrant.DevOptions{
		Instructions: strings.TrimSpace(devInstructions),
		Layer:        layered,
	}).Route(ctx)
}

func (a *App) DevDep(dst, src *app.Context) (*app.DevDep, error) {
	return vagrant.DevDep(dst, src, &vagrant.DevDepOptions{})
}

const devInstructions = `
A development environment has been created for writing a generic
Ruby-based app.

Ruby is pre-installed. To work on your project, edit files locally on your
own machine. The file changes will be synced to the development environment.

When you're ready to build your project, run 'otto dev ssh' to enter
the development environment. You'll be placed directly into the working
directory where you can run 'bundle' and 'ruby' as you normally would.

You can access any running web application using the IP above.
`
<commit_msg>app/ruby: Implicit depdendencies for dalli/pg<commit_after>package rubyapp

import (
	"fmt"
	"log"
	"path/filepath"
	"strings"

	"github.com/hashicorp/otto/app"
	"github.com/hashicorp/otto/appfile"
	rubySP "github.com/hashicorp/otto/builtin/scriptpack/ruby"
	stdSP "github.com/hashicorp/otto/builtin/scriptpack/stdlib"
	"github.com/hashicorp/otto/helper/bindata"
	"github.com/hashicorp/otto/helper/compile"
	"github.com/hashicorp/otto/helper/oneline"
	"github.com/hashicorp/otto/helper/packer"
	"github.com/hashicorp/otto/helper/schema"
	"github.com/hashicorp/otto/helper/terraform"
	"github.com/hashicorp/otto/helper/vagrant"
	"github.com/hashicorp/otto/scriptpack"
)

//go:generate go-bindata -pkg=rubyapp -nomemcopy -nometadata ./data/...

// App is an implementation of app.App
type App struct{}

func (a *App) Meta() (*app.Meta, error) {
	return Meta, nil
}

func (a *App) Implicit(ctx *app.Context) (*appfile.File, error) {
	// depMap is our mapping of gem to dependency URL
	depMap := map[string]string{
		"dalli": "github.com/hashicorp/otto/examples/memcached",
		"pg":    "github.com/hashicorp/otto/examples/postgresql",
		"redis": "github.com/hashicorp/otto/examples/redis",
	}

	// used keeps track of dependencies we've used so we don't
	// double-up on dependencies
	used := map[string]struct{}{}

	// Get the path to the working directory
	dir := filepath.Dir(ctx.Appfile.Path)
	log.Printf("[DEBUG] app: implicit check path: %s", dir)

	// If we have certain gems, add the dependencies
	var deps []*appfile.Dependency
	for k, v := range depMap {
		// If we already used v, then don't do it
		if _, ok := used[v]; ok {
			continue
		}

		// If we don't have the gem, then nothing to do
		log.Printf("[DEBUG] app: checking for Gem: %s", k)
		ok, err := HasGem(dir, k)
		if err != nil {
			return nil, err
		}
		if !ok {
			log.Printf("[DEBUG] app: Gem not found: %s", k)
			continue
		}
		log.Printf("[INFO] app: found Gem '%s', adding dep: %s", k, v)

		// We have it! Add the implicit
		deps = append(deps, &appfile.Dependency{
			Source: v,
		})
		used[v] = struct{}{}
	}

	// Build an implicit Appfile if we have deps
	var result *appfile.File
	if len(deps) > 0 {
		result = &appfile.File{
			Application: &appfile.Application{
				Dependencies: deps,
			},
		}
	}

	return result, nil
}

func (a *App) Compile(ctx *app.Context) (*app.CompileResult, error) {
	var opts compile.AppOptions
	custom := &customizations{Opts: &opts}
	opts = compile.AppOptions{
		Ctx: ctx,
		Result: &app.CompileResult{
			Version: 1,
		},
		Bindata: &bindata.Data{
			Asset:    Asset,
			AssetDir: AssetDir,
			Context:  map[string]interface{}{},
		},
		ScriptPacks: []*scriptpack.ScriptPack{
			&stdSP.ScriptPack,
			&rubySP.ScriptPack,
		},
		Customization: (&compile.Customization{
			Callback: custom.process,
			Schema: map[string]*schema.FieldSchema{
				"ruby_version": &schema.FieldSchema{
					Type:        schema.TypeString,
					Default:     "detect",
					Description: "Ruby version to install",
				},
			},
		}).Merge(compile.VagrantCustomizations(&opts)),
	}

	return compile.App(&opts)
}

func (a *App) Build(ctx *app.Context) error {
	return packer.Build(ctx, &packer.BuildOptions{
		InfraOutputMap: map[string]string{
			"region":        "aws_region",
			"vpc_id":        "aws_vpc_id",
			"subnet_public": "aws_subnet_id",
		},
	})
}

func (a *App) Deploy(ctx *app.Context) error {
	return terraform.Deploy(&terraform.DeployOptions{
		InfraOutputMap: map[string]string{
			"region":         "aws_region",
			"subnet-private": "private_subnet_id",
			"subnet-public":  "public_subnet_id",
		},
	}).Route(ctx)
}

func (a *App) Dev(ctx *app.Context) error {
	var layered *vagrant.Layered

	// We only setup a layered environment if we've recompiled since
	// version 0. If we're still at version 0 then we have to use the
	// non-layered dev environment.
	if ctx.CompileResult.Version > 0 {
		// Read the go version, since we use that for our layer
		version, err := oneline.Read(filepath.Join(ctx.Dir, "dev", "ruby_version"))
		if err != nil {
			return err
		}

		// Setup layers
		layered, err = vagrant.DevLayered(ctx, []*vagrant.Layer{
			&vagrant.Layer{
				ID:          fmt.Sprintf("ruby%s", version),
				Vagrantfile: filepath.Join(ctx.Dir, "dev", "layer-base", "Vagrantfile"),
			},
		})
		if err != nil {
			return err
		}
	}

	// Build the actual development environment
	return vagrant.Dev(&vagrant.DevOptions{
		Instructions: strings.TrimSpace(devInstructions),
		Layer:        layered,
	}).Route(ctx)
}

func (a *App) DevDep(dst, src *app.Context) (*app.DevDep, error) {
	return vagrant.DevDep(dst, src, &vagrant.DevDepOptions{})
}

const devInstructions = `
A development environment has been created for writing a generic
Ruby-based app.

Ruby is pre-installed. To work on your project, edit files locally on your
own machine. The file changes will be synced to the development environment.

When you're ready to build your project, run 'otto dev ssh' to enter
the development environment. You'll be placed directly into the working
directory where you can run 'bundle' and 'ruby' as you normally would.

You can access any running web application using the IP above.
`
<|endoftext|>