<commit_before>package revel

import (
	"fmt"
	"github.com/robfig/config"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

const (
	CurrentLocaleRenderArg = "currentLocale" // The key for the current locale render arg value

	messageFilesDirectory = "messages"
	messageFilePattern    = `^\w+.[a-zA-Z]{2}$`
	unknownValueFormat    = "??? %s ???"
	defaultLanguageOption = "i18n.default_language"
	localeCookieConfigKey = "i18n.cookie"
)

var (
	// All currently loaded message configs.
	messages map[string]*config.Config
)

// Return all currently loaded message languages.
func MessageLanguages() []string {
	languages := make([]string, len(messages))
	i := 0
	for language, _ := range messages {
		languages[i] = language
		i++
	}
	return languages
}

// Perform a message look-up for the given locale and message using the given arguments.
//
// When either an unknown locale or message is detected, a specially formatted string is returned.
func Message(locale, message string, args ...interface{}) string {
	language, region := parseLocale(locale)
	TRACE.Printf("Resolving message '%s' for language '%s' and region '%s'", message, language, region)

	messageConfig, knownLanguage := messages[language]
	if !knownLanguage {
		WARN.Printf("Unsupported language for locale '%s' and message '%s', trying default language", locale, message)

		if defaultLanguage, found := Config.String(defaultLanguageOption); found {
			TRACE.Printf("Using default language '%s'", defaultLanguage)

			messageConfig, knownLanguage = messages[defaultLanguage]
			if !knownLanguage {
				WARN.Printf("Unsupported default language for locale '%s' and message '%s'", defaultLanguage, message)
				return fmt.Sprintf(unknownValueFormat, message)
			}
		} else {
			WARN.Printf("Unable to find default language option (%s); messages for unsupported locales will never be translated", defaultLanguageOption)
			return fmt.Sprintf(unknownValueFormat, message)
		}
	}

	// This works because unlike the goconfig documentation suggests it will actually
	// try to resolve message in DEFAULT if it did not find it in the given section.
	value, error := messageConfig.String(region, message)
	if error != nil {
		WARN.Printf("Unknown message '%s' for locale '%s'", message, locale)
		return fmt.Sprintf(unknownValueFormat, message)
	}

	if len(args) > 0 {
		TRACE.Printf("Arguments detected, formatting '%s' with %v", value, args)
		value = fmt.Sprintf(value, args...)
	}

	return value
}

func parseLocale(locale string) (language, region string) {
	if strings.Contains(locale, "-") {
		languageAndRegion := strings.Split(locale, "-")
		return languageAndRegion[0], languageAndRegion[1]
	}

	return locale, ""
}

// Recursively read and cache all available messages from all message files on the given path.
func loadMessages(path string) {
	messages = make(map[string]*config.Config)

	if error := filepath.Walk(path, loadMessageFile); error != nil && !os.IsNotExist(error) {
		ERROR.Println("Error reading messages files:", error)
	}
}

// Load a single message file
func loadMessageFile(path string, info os.FileInfo, osError error) error {
	if osError != nil {
		return osError
	}
	if info.IsDir() {
		return nil
	}

	if matched, _ := regexp.MatchString(messageFilePattern, info.Name()); matched {
		if config, error := parseMessagesFile(path); error != nil {
			return error
		} else {
			locale := parseLocaleFromFileName(info.Name())

			// If we have already parsed a message file for this locale, merge both
			if _, exists := messages[locale]; exists {
				messages[locale].Merge(config)
				TRACE.Printf("Successfully merged messages for locale '%s'", locale)
			} else {
				messages[locale] = config
			}

			TRACE.Println("Successfully loaded messages from file", info.Name())
		}
	} else {
		TRACE.Printf("Ignoring file %s because it did not have a valid extension", info.Name())
	}

	return nil
}

func parseMessagesFile(path string) (messageConfig *config.Config, error error) {
	messageConfig, error = config.ReadDefault(path)
	return
}

func parseLocaleFromFileName(file string) string {
	extension := filepath.Ext(file)[1:]
	return strings.ToLower(extension)
}

func init() {
	OnAppStart(func() {
		loadMessages(filepath.Join(BasePath, messageFilesDirectory))
	})
}

func I18nFilter(c *Controller, fc []Filter) {
	if foundCookie, cookieValue := hasLocaleCookie(c.Request); foundCookie {
		TRACE.Printf("Found locale cookie value: %s", cookieValue)
		setCurrentLocaleControllerArguments(c, cookieValue)
	} else if foundHeader, headerValue := hasAcceptLanguageHeader(c.Request); foundHeader {
		TRACE.Printf("Found Accept-Language header value: %s", headerValue)
		setCurrentLocaleControllerArguments(c, headerValue)
	} else {
		TRACE.Println("Unable to find locale in cookie or header, using empty string")
		setCurrentLocaleControllerArguments(c, "")
	}
	fc[0](c, fc[1:])
}

// Set the current locale controller argument (CurrentLocaleControllerArg) with the given locale.
func setCurrentLocaleControllerArguments(c *Controller, locale string) {
	c.Request.Locale = locale
	c.RenderArgs[CurrentLocaleRenderArg] = locale
}

// Determine whether the given request has valid Accept-Language value.
//
// Assumes that the accept languages stored in the request are sorted according to quality, with top
// quality first in the slice.
func hasAcceptLanguageHeader(request *Request) (bool, string) {
	if request.AcceptLanguages != nil && len(request.AcceptLanguages) > 0 {
		return true, request.AcceptLanguages[0].Language
	}

	return false, ""
}

// Determine whether the given request has a valid language cookie value.
func hasLocaleCookie(request *Request) (bool, string) {
	if request != nil && request.Cookies() != nil {
		name := Config.StringDefault(localeCookieConfigKey, CookiePrefix+"_LANG")
		if cookie, error := request.Cookie(name); error == nil {
			return true, cookie.Value
		} else {
			TRACE.Printf("Unable to read locale cookie with name '%s': %s", name, error.Error())
		}
	}

	return false, ""
}
<commit_msg>Fix #250<commit_after>package revel

import (
	"fmt"
	"github.com/robfig/config"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

const (
	CurrentLocaleRenderArg = "currentLocale" // The key for the current locale render arg value

	messageFilesDirectory = "messages"
	messageFilePattern    = `^\w+.[a-zA-Z]{2}$`
	unknownValueFormat    = "??? %s ???"
	defaultLanguageOption = "i18n.default_language"
	localeCookieConfigKey = "i18n.cookie"
)

var (
	// All currently loaded message configs.
	messages map[string]*config.Config
)

// Return all currently loaded message languages.
func MessageLanguages() []string {
	languages := make([]string, len(messages))
	i := 0
	for language, _ := range messages {
		languages[i] = language
		i++
	}
	return languages
}

// Perform a message look-up for the given locale and message using the given arguments.
//
// When either an unknown locale or message is detected, a specially formatted string is returned.
func Message(locale, message string, args ...interface{}) string {
	language, region := parseLocale(locale)
	TRACE.Printf("Resolving message '%s' for language '%s' and region '%s'", message, language, region)

	messageConfig, knownLanguage := messages[language]
	if !knownLanguage {
		TRACE.Printf("Unsupported language for locale '%s' and message '%s', trying default language", locale, message)

		if defaultLanguage, found := Config.String(defaultLanguageOption); found {
			TRACE.Printf("Using default language '%s'", defaultLanguage)

			messageConfig, knownLanguage = messages[defaultLanguage]
			if !knownLanguage {
				WARN.Printf("Unsupported default language for locale '%s' and message '%s'", defaultLanguage, message)
				return fmt.Sprintf(unknownValueFormat, message)
			}
		} else {
			WARN.Printf("Unable to find default language option (%s); messages for unsupported locales will never be translated", defaultLanguageOption)
			return fmt.Sprintf(unknownValueFormat, message)
		}
	}

	// This works because unlike the goconfig documentation suggests it will actually
	// try to resolve message in DEFAULT if it did not find it in the given section.
	value, error := messageConfig.String(region, message)
	if error != nil {
		WARN.Printf("Unknown message '%s' for locale '%s'", message, locale)
		return fmt.Sprintf(unknownValueFormat, message)
	}

	if len(args) > 0 {
		TRACE.Printf("Arguments detected, formatting '%s' with %v", value, args)
		value = fmt.Sprintf(value, args...)
	}

	return value
}

func parseLocale(locale string) (language, region string) {
	if strings.Contains(locale, "-") {
		languageAndRegion := strings.Split(locale, "-")
		return languageAndRegion[0], languageAndRegion[1]
	}

	return locale, ""
}

// Recursively read and cache all available messages from all message files on the given path.
func loadMessages(path string) {
	messages = make(map[string]*config.Config)

	if error := filepath.Walk(path, loadMessageFile); error != nil && !os.IsNotExist(error) {
		ERROR.Println("Error reading messages files:", error)
	}
}

// Load a single message file
func loadMessageFile(path string, info os.FileInfo, osError error) error {
	if osError != nil {
		return osError
	}
	if info.IsDir() {
		return nil
	}

	if matched, _ := regexp.MatchString(messageFilePattern, info.Name()); matched {
		if config, error := parseMessagesFile(path); error != nil {
			return error
		} else {
			locale := parseLocaleFromFileName(info.Name())

			// If we have already parsed a message file for this locale, merge both
			if _, exists := messages[locale]; exists {
				messages[locale].Merge(config)
				TRACE.Printf("Successfully merged messages for locale '%s'", locale)
			} else {
				messages[locale] = config
			}

			TRACE.Println("Successfully loaded messages from file", info.Name())
		}
	} else {
		TRACE.Printf("Ignoring file %s because it did not have a valid extension", info.Name())
	}

	return nil
}

func parseMessagesFile(path string) (messageConfig *config.Config, error error) {
	messageConfig, error = config.ReadDefault(path)
	return
}

func parseLocaleFromFileName(file string) string {
	extension := filepath.Ext(file)[1:]
	return strings.ToLower(extension)
}

func init() {
	OnAppStart(func() {
		loadMessages(filepath.Join(BasePath, messageFilesDirectory))
	})
}

func I18nFilter(c *Controller, fc []Filter) {
	if foundCookie, cookieValue := hasLocaleCookie(c.Request); foundCookie {
		TRACE.Printf("Found locale cookie value: %s", cookieValue)
		setCurrentLocaleControllerArguments(c, cookieValue)
	} else if foundHeader, headerValue := hasAcceptLanguageHeader(c.Request); foundHeader {
		TRACE.Printf("Found Accept-Language header value: %s", headerValue)
		setCurrentLocaleControllerArguments(c, headerValue)
	} else {
		TRACE.Println("Unable to find locale in cookie or header, using empty string")
		setCurrentLocaleControllerArguments(c, "")
	}
	fc[0](c, fc[1:])
}

// Set the current locale controller argument (CurrentLocaleControllerArg) with the given locale.
func setCurrentLocaleControllerArguments(c *Controller, locale string) {
	c.Request.Locale = locale
	c.RenderArgs[CurrentLocaleRenderArg] = locale
}

// Determine whether the given request has valid Accept-Language value.
//
// Assumes that the accept languages stored in the request are sorted according to quality, with top
// quality first in the slice.
func hasAcceptLanguageHeader(request *Request) (bool, string) {
	if request.AcceptLanguages != nil && len(request.AcceptLanguages) > 0 {
		return true, request.AcceptLanguages[0].Language
	}

	return false, ""
}

// Determine whether the given request has a valid language cookie value.
func hasLocaleCookie(request *Request) (bool, string) {
	if request != nil && request.Cookies() != nil {
		name := Config.StringDefault(localeCookieConfigKey, CookiePrefix+"_LANG")
		if cookie, error := request.Cookie(name); error == nil {
			return true, cookie.Value
		} else {
			TRACE.Printf("Unable to read locale cookie with name '%s': %s", name, error.Error())
		}
	}

	return false, ""
}
<|endoftext|>


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

// Package spinner is a simple package to add a spinner/progress indicator to your application.
package spinner

import (
	"errors"
	"fmt"
	"io"
	"os"
	"strconv"
	"time"
	"unicode/utf8"

	"github.com/fatih/color"
)

// CharSets contains the available character sets
var CharSets = [][]string{
	{"←", "↖", "↑", "↗", "→", "↘", "↓", "↙"},
	{"▁", "▃", "▄", "▅", "▆", "▇", "█", "▇", "▆", "▅", "▄", "▃", "▁"},
	{"▖", "▘", "▝", "▗"},
	{"┤", "┘", "┴", "└", "├", "┌", "┬", "┐"},
	{"◢", "◣", "◤", "◥"},
	{"◰", "◳", "◲", "◱"},
	{"◴", "◷", "◶", "◵"},
	{"◐", "◓", "◑", "◒"},
	{".", "o", "O", "@", "*"},
	{"|", "/", "-", "\\"},
	{"◡◡", "⊙⊙", "◠◠"},
	{"⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"},
	{">))'>", " >))'>", "  >))'>", "   >))'>", "    >))'>", "   <'((<", "  <'((<", " <'((<"},
	{"⠁", "⠂", "⠄", "⡀", "⢀", "⠠", "⠐", "⠈"},
	{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"},
	{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"},
	{"▉", "▊", "▋", "▌", "▍", "▎", "▏", "▎", "▍", "▌", "▋", "▊", "▉"},
	{"■", "□", "▪", "▫"},
	{"←", "↑", "→", "↓"},
	{"╫", "╪"},
	{"⇐", "⇖", "⇑", "⇗", "⇒", "⇘", "⇓", "⇙"},
	{"⠁", "⠁", "⠉", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠤", "⠄", "⠄", "⠤", "⠠", "⠠", "⠤", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋", "⠉", "⠈", "⠈"},
	{"⠈", "⠉", "⠋", "⠓", "⠒", "⠐", "⠐", "⠒", "⠖", "⠦", "⠤", "⠠", "⠠", "⠤", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋", "⠉", "⠈"},
	{"⠁", "⠉", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠤", "⠄", "⠄", "⠤", "⠴", "⠲", "⠒", "⠂", "⠂", "⠒", "⠚", "⠙", "⠉", "⠁"},
	{"⠋", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋"},
	{"ｦ", "ｧ", "ｨ", "ｩ", "ｪ", "ｫ", "ｬ", "ｭ", "ｮ", "ｯ", "ｱ", "ｲ", "ｳ", "ｴ", "ｵ", "ｶ", "ｷ", "ｸ", "ｹ", "ｺ", "ｻ", "ｼ", "ｽ", "ｾ", "ｿ", "ﾀ", "ﾁ", "ﾂ", "ﾃ", "ﾄ", "ﾅ", "ﾆ", "ﾇ", "ﾈ", "ﾉ", "ﾊ", "ﾋ", "ﾌ", "ﾍ", "ﾎ", "ﾏ", "ﾐ", "ﾑ", "ﾒ", "ﾓ", "ﾔ", "ﾕ", "ﾖ", "ﾗ", "ﾘ", "ﾙ", "ﾚ", "ﾛ", "ﾜ", "ﾝ"},
	{".", "..", "..."},
	{"▁", "▂", "▃", "▄", "▅", "▆", "▇", "█", "▉", "▊", "▋", "▌", "▍", "▎", "▏", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█", "▇", "▆", "▅", "▄", "▃", "▂", "▁"},
	{".", "o", "O", "°", "O", "o", "."},
	{"+", "x"},
	{"v", "<", "^", ">"},
	{">>--->", " >>--->", "  >>--->", "   >>--->", "    >>--->", "    <---<<", "   <---<<", "  <---<<", " <---<<", "<---<<"},
	{"|", "||", "|||", "||||", "|||||", "|||||||", "||||||||", "|||||||", "||||||", "|||||", "||||", "|||", "||", "|"},
	{"[          ]", "[=         ]", "[==        ]", "[===       ]", "[====      ]", "[=====     ]", "[======    ]", "[=======   ]", "[========  ]", "[========= ]", "[==========]"},
	{"(*---------)", "(-*--------)", "(--*-------)", "(---*------)", "(----*-----)", "(-----*----)", "(------*---)", "(-------*--)", "(--------*-)", "(---------*)"},
	{"█▒▒▒▒▒▒▒▒▒", "███▒▒▒▒▒▒▒", "█████▒▒▒▒▒", "███████▒▒▒", "██████████"},
}

// state is a type for the spinner status
type state uint8

// Spinner struct to hold the provided options
type Spinner struct {
	chars    []string                      // chosen character set
	Delay    time.Duration                 // speed of the spinner
	Prefix   string                        // Text preppended to the spinner
	Suffix   string                        // Text appended to the spinner
	stopChan chan bool                     // channel used to stop the spinner
	ST       state                         // spinner status
	w        io.Writer                     // to make testing better
	color    func(a ...interface{}) string // default color is white
}

//go:generate stringer -type=state
const (
	stopped state = iota
	running
)

// validColors holds an array of the only colors allowed
var validColors = []string{"red", "green", "yellow", "blue", "magenta", "cyan", "white"}

// validColor will make sure the given color is actually allowed
func validColor(c string) bool {
	for _, i := range validColors {
		if c == i {
			return true
		}
	}
	return false
}

// New provides a pointer to an instance of Spinner with the supplied options
func New(c []string, t time.Duration) *Spinner {
	s := &Spinner{
		Delay:    t,
		stopChan: make(chan bool, 1),
		color:    color.New(color.FgWhite).SprintFunc(),
		w:        os.Stdout,
	}
	s.UpdateCharSet(c)
	return s
}

// Start will start the spinner
func (s *Spinner) Start() {
	if s.ST == running {
		return
	}
	s.ST = running
	go func() {
		for {
			for i := 0; i < len(s.chars); i++ {
				select {
				case <-s.stopChan:
					return
				default:
					out := fmt.Sprintf("%s%s%s ", s.Prefix, s.color(s.chars[i]), s.Suffix)
					fmt.Fprint(s.w, out)
					time.Sleep(s.Delay)
					erase(s.w, out)
				}
			}
		}
	}()
}

// erase deletes written characters
func erase(w io.Writer, a string) {
	n := utf8.RuneCountInString(a)
	for i := 0; i < n; i++ {
		fmt.Fprintf(w, "\b")
	}
}

// Color will set the struct field for the given color to be used
func (s *Spinner) Color(c string) error {
	if validColor(c) {
		switch {
		case c == "red":
			s.color = color.New(color.FgRed).SprintFunc()
			s.Restart()
		case c == "yellow":
			s.color = color.New(color.FgYellow).SprintFunc()
			s.Restart()
		case c == "green":
			s.color = color.New(color.FgGreen).SprintFunc()
			s.Restart()
		case c == "magenta":
			s.color = color.New(color.FgMagenta).SprintFunc()
			s.Restart()
		case c == "blue":
			s.color = color.New(color.FgBlue).SprintFunc()
			s.Restart()
		case c == "cyan":
			s.color = color.New(color.FgCyan).SprintFunc()
			s.Restart()
		case c == "white":
			s.color = color.New(color.FgWhite).SprintFunc()
			s.Restart()
		default:
			return errors.New("invalid color")
		}
	}
	return nil
}

// Stop stops the spinner
func (s *Spinner) Stop() {
	if s.ST == running {
		s.stopChan <- true
		s.ST = stopped
	}
}

// Restart will stop and start the spinner
func (s *Spinner) Restart() {
	s.Stop()
	s.Start()
}

// Reverse will reverse the order of the slice assigned to that spinner
func (s *Spinner) Reverse() {
	for i, j := 0, len(s.chars)-1; i < j; i, j = i+1, j-1 {
		s.chars[i], s.chars[j] = s.chars[j], s.chars[i]
	}
}

// UpdateSpeed is a convenience function to not have to make you
//create a new instance of the Spinner
func (s *Spinner) UpdateSpeed(delay time.Duration) { s.Delay = delay }

// UpdateCharSet will change the previously select character set to
// the provided one
func (s *Spinner) UpdateCharSet(chars []string) {
	// so that changes to the slice outside of the spinner don't change it
	// unexpectedly, create an internal copy
	n := make([]string, len(chars))
	copy(n, chars)
	s.chars = n
}

// GenerateNumberSequence will generate a slice of integers at the
// provided length and convert them each to a string
func GenerateNumberSequence(length int) []string {
	//numSeq := make([]string, 0)
	var numSeq []string
	for i := 0; i < length; i++ {
		numSeq = append(numSeq, strconv.Itoa(i))
	}
	return numSeq
}
<commit_msg>update color switch<commit_after>// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package spinner is a simple package to add a spinner/progress indicator to your application.
package spinner

import (
	"errors"
	"fmt"
	"io"
	"os"
	"strconv"
	"time"
	"unicode/utf8"

	"github.com/fatih/color"
)

// CharSets contains the available character sets
var CharSets = [][]string{
	{"←", "↖", "↑", "↗", "→", "↘", "↓", "↙"},
	{"▁", "▃", "▄", "▅", "▆", "▇", "█", "▇", "▆", "▅", "▄", "▃", "▁"},
	{"▖", "▘", "▝", "▗"},
	{"┤", "┘", "┴", "└", "├", "┌", "┬", "┐"},
	{"◢", "◣", "◤", "◥"},
	{"◰", "◳", "◲", "◱"},
	{"◴", "◷", "◶", "◵"},
	{"◐", "◓", "◑", "◒"},
	{".", "o", "O", "@", "*"},
	{"|", "/", "-", "\\"},
	{"◡◡", "⊙⊙", "◠◠"},
	{"⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"},
	{">))'>", " >))'>", "  >))'>", "   >))'>", "    >))'>", "   <'((<", "  <'((<", " <'((<"},
	{"⠁", "⠂", "⠄", "⡀", "⢀", "⠠", "⠐", "⠈"},
	{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"},
	{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"},
	{"▉", "▊", "▋", "▌", "▍", "▎", "▏", "▎", "▍", "▌", "▋", "▊", "▉"},
	{"■", "□", "▪", "▫"},
	{"←", "↑", "→", "↓"},
	{"╫", "╪"},
	{"⇐", "⇖", "⇑", "⇗", "⇒", "⇘", "⇓", "⇙"},
	{"⠁", "⠁", "⠉", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠤", "⠄", "⠄", "⠤", "⠠", "⠠", "⠤", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋", "⠉", "⠈", "⠈"},
	{"⠈", "⠉", "⠋", "⠓", "⠒", "⠐", "⠐", "⠒", "⠖", "⠦", "⠤", "⠠", "⠠", "⠤", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋", "⠉", "⠈"},
	{"⠁", "⠉", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠤", "⠄", "⠄", "⠤", "⠴", "⠲", "⠒", "⠂", "⠂", "⠒", "⠚", "⠙", "⠉", "⠁"},
	{"⠋", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋"},
	{"ｦ", "ｧ", "ｨ", "ｩ", "ｪ", "ｫ", "ｬ", "ｭ", "ｮ", "ｯ", "ｱ", "ｲ", "ｳ", "ｴ", "ｵ", "ｶ", "ｷ", "ｸ", "ｹ", "ｺ", "ｻ", "ｼ", "ｽ", "ｾ", "ｿ", "ﾀ", "ﾁ", "ﾂ", "ﾃ", "ﾄ", "ﾅ", "ﾆ", "ﾇ", "ﾈ", "ﾉ", "ﾊ", "ﾋ", "ﾌ", "ﾍ", "ﾎ", "ﾏ", "ﾐ", "ﾑ", "ﾒ", "ﾓ", "ﾔ", "ﾕ", "ﾖ", "ﾗ", "ﾘ", "ﾙ", "ﾚ", "ﾛ", "ﾜ", "ﾝ"},
	{".", "..", "..."},
	{"▁", "▂", "▃", "▄", "▅", "▆", "▇", "█", "▉", "▊", "▋", "▌", "▍", "▎", "▏", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█", "▇", "▆", "▅", "▄", "▃", "▂", "▁"},
	{".", "o", "O", "°", "O", "o", "."},
	{"+", "x"},
	{"v", "<", "^", ">"},
	{">>--->", " >>--->", "  >>--->", "   >>--->", "    >>--->", "    <---<<", "   <---<<", "  <---<<", " <---<<", "<---<<"},
	{"|", "||", "|||", "||||", "|||||", "|||||||", "||||||||", "|||||||", "||||||", "|||||", "||||", "|||", "||", "|"},
	{"[          ]", "[=         ]", "[==        ]", "[===       ]", "[====      ]", "[=====     ]", "[======    ]", "[=======   ]", "[========  ]", "[========= ]", "[==========]"},
	{"(*---------)", "(-*--------)", "(--*-------)", "(---*------)", "(----*-----)", "(-----*----)", "(------*---)", "(-------*--)", "(--------*-)", "(---------*)"},
	{"█▒▒▒▒▒▒▒▒▒", "███▒▒▒▒▒▒▒", "█████▒▒▒▒▒", "███████▒▒▒", "██████████"},
}

// state is a type for the spinner status
type state uint8

// Spinner struct to hold the provided options
type Spinner struct {
	chars    []string                      // chosen character set
	Delay    time.Duration                 // speed of the spinner
	Prefix   string                        // Text preppended to the spinner
	Suffix   string                        // Text appended to the spinner
	stopChan chan bool                     // channel used to stop the spinner
	ST       state                         // spinner status
	w        io.Writer                     // to make testing better
	color    func(a ...interface{}) string // default color is white
}

//go:generate stringer -type=state
const (
	stopped state = iota
	running
)

// validColors holds an array of the only colors allowed
var validColors = []string{"red", "green", "yellow", "blue", "magenta", "cyan", "white"}

// validColor will make sure the given color is actually allowed
func validColor(c string) bool {
	for _, i := range validColors {
		if c == i {
			return true
		}
	}
	return false
}

// New provides a pointer to an instance of Spinner with the supplied options
func New(c []string, t time.Duration) *Spinner {
	s := &Spinner{
		Delay:    t,
		stopChan: make(chan bool, 1),
		color:    color.New(color.FgWhite).SprintFunc(),
		w:        os.Stdout,
	}
	s.UpdateCharSet(c)
	return s
}

// Start will start the spinner
func (s *Spinner) Start() {
	if s.ST == running {
		return
	}
	s.ST = running
	go func() {
		for {
			for i := 0; i < len(s.chars); i++ {
				select {
				case <-s.stopChan:
					return
				default:
					out := fmt.Sprintf("%s%s%s ", s.Prefix, s.color(s.chars[i]), s.Suffix)
					fmt.Fprint(s.w, out)
					time.Sleep(s.Delay)
					erase(s.w, out)
				}
			}
		}
	}()
}

// erase deletes written characters
func erase(w io.Writer, a string) {
	n := utf8.RuneCountInString(a)
	for i := 0; i < n; i++ {
		fmt.Fprintf(w, "\b")
	}
}

// Color will set the struct field for the given color to be used
func (s *Spinner) Color(c string) error {
	if validColor(c) {
		switch c {
		case "red":
			s.color = color.New(color.FgRed).SprintFunc()
			s.Restart()
		case "yellow":
			s.color = color.New(color.FgYellow).SprintFunc()
			s.Restart()
		case "green":
			s.color = color.New(color.FgGreen).SprintFunc()
			s.Restart()
		case "magenta":
			s.color = color.New(color.FgMagenta).SprintFunc()
			s.Restart()
		case "blue":
			s.color = color.New(color.FgBlue).SprintFunc()
			s.Restart()
		case "cyan":
			s.color = color.New(color.FgCyan).SprintFunc()
			s.Restart()
		case "white":
			s.color = color.New(color.FgWhite).SprintFunc()
			s.Restart()
		default:
			return errors.New("invalid color")
		}
	}
	return nil
}

// Stop stops the spinner
func (s *Spinner) Stop() {
	if s.ST == running {
		s.stopChan <- true
		s.ST = stopped
	}
}

// Restart will stop and start the spinner
func (s *Spinner) Restart() {
	s.Stop()
	s.Start()
}

// Reverse will reverse the order of the slice assigned to that spinner
func (s *Spinner) Reverse() {
	for i, j := 0, len(s.chars)-1; i < j; i, j = i+1, j-1 {
		s.chars[i], s.chars[j] = s.chars[j], s.chars[i]
	}
}

// UpdateSpeed is a convenience function to not have to make you
//create a new instance of the Spinner
func (s *Spinner) UpdateSpeed(delay time.Duration) { s.Delay = delay }

// UpdateCharSet will change the previously select character set to
// the provided one
func (s *Spinner) UpdateCharSet(chars []string) {
	// so that changes to the slice outside of the spinner don't change it
	// unexpectedly, create an internal copy
	n := make([]string, len(chars))
	copy(n, chars)
	s.chars = n
}

// GenerateNumberSequence will generate a slice of integers at the
// provided length and convert them each to a string
func GenerateNumberSequence(length int) []string {
	//numSeq := make([]string, 0)
	var numSeq []string
	for i := 0; i < length; i++ {
		numSeq = append(numSeq, strconv.Itoa(i))
	}
	return numSeq
}
<|endoftext|>


<commit_before>// Copyright (c) 2016 SEkiSoft
// See License.txt

package store

import (
	"testing"

	"github.com/sekisoft/gogogo/utils"
)

var store Store

func TestSetUp() {
	if store == nil {
		utils.LoadConfig()

		store = NewSqlStore()
	}
}

func TestSqlStore(t *testing.T) {
	TestSetUp()

	if store == nil {
		t.Fatal("should not fail")
	}
}

func TestSqlStoreClose(t *testing.T) {
	TestSetUp()

	store.Close()

	result := <-store.Game().GetAll()

	if result.Err == nil {
		t.Fatal("should have errored")
	}
}
<commit_msg>fixed bad import (#110)<commit_after>// Copyright (c) 2016 SEkiSoft
// See License.txt

package store

import (
	"testing"

	"github.com/SEkiSoft/gogogo/utils"
)

var store Store

func TestSetUp() {
	if store == nil {
		utils.LoadConfig()

		store = NewSqlStore()
	}
}

func TestSqlStore(t *testing.T) {
	TestSetUp()

	if store == nil {
		t.Fatal("should not fail")
	}
}

func TestSqlStoreClose(t *testing.T) {
	TestSetUp()

	store.Close()

	result := <-store.Game().GetAll()

	if result.Err == nil {
		t.Fatal("should have errored")
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/binary"
	"flag"
	"fmt"
	"net"
	"os/exec"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"
	"unsafe"

	"github.com/xthexder/go-jack"
	"github.com/xthexder/rawstreamer"
)

var Client *jack.Client
var Ports []*jack.Port
var Buffers []unsafe.Pointer
var BufferPool chan []jack.AudioSample
var Listener net.Listener
var ClientWaitGroup sync.WaitGroup
var ShuttingDown chan struct{}

func process(nframes uint32) int {
	lsamples := Ports[0].GetBuffer(nframes)
	rsamples := Ports[1].GetBuffer(nframes)
	for client, bufp := range Buffers {
		buf := (*chan []jack.AudioSample)(atomic.LoadPointer(&bufp))
		if buf == nil {
			continue
		}

		n := 0
		for n < len(lsamples) {
			buffer := <-BufferPool
			nl := copy(buffer[:len(buffer)/2], lsamples[n:])
			buffer = buffer[:nl*2]
			copy(buffer[nl:], rsamples[n:])
			n += nl

			select {
			case *buf <- buffer:
			default:
				fmt.Println("Channel full for client:", client)
				n = len(lsamples)
			}
		}
	}
	return 0
}

func initPortMirror() {
	if len(mirror) <= 0 {
		return
	}

	ports := Client.GetPorts("^"+mirror, Ports[0].GetType(), jack.PortIsInput)
	for i, name := range ports {
		if i < len(Ports) {
			port := Client.GetPortByName(name)
			connections := port.GetConnections()
			for _, conn := range connections {
				Client.Connect(conn, Ports[i].GetName())
			}
		}
	}
}

func updateProcs() {
	if len(procName) <= 0 {
		return
	}

	ports := Client.GetPorts("^alsa-jack\\.jackP\\.", Ports[0].GetType(), jack.PortIsOutput)
	procs := make(map[string]int)
	for _, name := range ports {
		clientName := strings.SplitN(name, ":", 2)[0]
		pidStr := strings.SplitN(clientName[16:], ".", 2)[0]
		pid, err := strconv.Atoi(pidStr)
		if err != nil {
			continue
		}
		procs[clientName] = pid
	}
	for clientName, pid := range procs {
		out, err := exec.Command("ps", "-p", strconv.Itoa(pid), "-o", "comm=").Output()
		if err != nil {
			fmt.Println("Error finding process:", err)
			return
		}
		proc := string(out)
		if strings.Contains(proc, procName) {
			ports = Client.GetPorts("^"+clientName+":", Ports[0].GetType(), jack.PortIsOutput)
			for i, name := range ports {
				if i < len(Ports) {
					Client.Connect(name, Ports[i].GetName())
				}
			}
		}
	}
}

func updateSources() {
	if len(source) <= 0 {
		return
	}

	ports := Client.GetPorts("^"+source, Ports[0].GetType(), jack.PortIsOutput)
	sources := make(map[string][]string)
	for _, name := range ports {
		clientName := strings.SplitN(name, ":", 2)[0]
		sources[clientName] = append(sources[clientName], name)
	}
	for _, names := range sources {
		if len(names) == 1 { // Mono source
			for _, port := range Ports {
				Client.Connect(names[0], port.GetName())
			}
		} else {
			for i, name := range names {
				if i < len(Ports) {
					Client.Connect(name, Ports[i].GetName())
				}
			}
		}
	}
}

func portRegistered(portId jack.PortId, registered bool) {
	if registered && !Client.IsPortMine(Client.GetPortById(portId)) {
		go updateProcs()
		go updateSources()
	}
}

func disconnectClients() {
	for _, bufp := range Buffers {
		buf := (*chan []jack.AudioSample)(atomic.LoadPointer(&bufp))
		if buf == nil {
			continue
		}
		close(*buf)
	}
}

func sampleRateChanged(sampleRate uint32) int {
	printStreamInfo()
	disconnectClients()
	return 0
}

func bufferSizeChanged(bufferSize uint32) int {
	atomic.StoreUint32(&jackBufferSize, bufferSize*2)
	return 0
}

func portConnect(portAId, portBId jack.PortId, connected bool) {
	portA := Client.GetPortById(portAId)
	portB := Client.GetPortById(portBId)
	if Client.IsPortMine(portB) {
		return
	}

	if len(mirror) > 0 && strings.HasPrefix(portB.GetName(), mirror) {
		clientName := portB.GetClientName()
		go func() {
			ports := Client.GetPorts("^"+clientName+":", Ports[0].GetType(), jack.PortIsInput)
			for i, name := range ports {
				if name == portB.GetName() && i < len(Ports) {
					if connected {
						Client.ConnectPorts(portA, Ports[i])
					} else {
						Client.DisconnectPorts(portA, Ports[i])
					}
					break
				}
			}
		}()
	}
}

func shutdown() {
	fmt.Println("Shutting down")
	close(ShuttingDown)
	Listener.Close()
	disconnectClients()
}

var ErrWriteTimeout = fmt.Errorf("write timeout")

func writeAligned(conn *net.TCPConn, buf []byte, timeout time.Time) error {
	conn.SetWriteDeadline(timeout)
	n, err := conn.Write(buf)
	if err != nil {
		if err2, ok := err.(*net.OpError); ok && err2.Timeout() {
			// Add extra padding to make sure we're still aligned
			align := n % (bits / 4)
			if align > 0 {
				fmt.Printf("Realigning: %d + %d\n", n, (bits/4)-align)
				conn.SetWriteDeadline(time.Time{})
				_, err = conn.Write(make([]byte, (bits/4)-align))
				if err != nil {
					return err
				}
			}
			return ErrWriteTimeout
		}
	}
	return err
}

func streamConnection(conn *net.TCPConn) {
	defer conn.Close()
	defer ClientWaitGroup.Done()
	bufi, buf := getBuffer()
	if buf == nil {
		conn.Write([]byte{'R', 0})
		conn.Close()
		return
	}

	defer atomic.StorePointer(&Buffers[bufi], nil)

	numBytes := bits / 8
	_, err := conn.Write([]byte{'R', 1, formatFlag, byte(numBytes)})
	if err != nil {
		return
	}
	bytes := make([]byte, 4)
	sampleRate := Client.GetSampleRate()
	endianness.PutUint32(bytes, sampleRate)
	_, err = conn.Write(bytes[:4])
	if err != nil {
		return
	}

	sample := make([]byte, bits/4)
	align := 8 % len(sample)
	if align > 0 {
		// Add extra padding to make the header an even number of samples in length
		_, err = conn.Write(sample[:len(sample)-align])
		if err != nil {
			return
		}
	}

	bytes = make([]byte, 0, int(Client.GetBufferSize())*2)
	for {
		buffer := <-buf
		if buffer == nil {
			return
		}

		lsamples := buffer[:len(buffer)/2]
		rsamples := buffer[len(buffer)/2:]

		for i := range lsamples {
			rawstreamer.WriteFloat32(sample[:numBytes], float32(lsamples[i]), formatFlag, endianness)
			rawstreamer.WriteFloat32(sample[numBytes:], float32(rsamples[i]), formatFlag, endianness)

			bytes = append(bytes, sample...)
		}

		err = writeAligned(conn, bytes, time.Now().Add(bufferLen))
		if err == ErrWriteTimeout {
			fmt.Println("Write timeout!")
		} else if err != nil {
			fmt.Printf("Sample write error: %v\n", err)
			returnToPool(buffer)
			return
		}

		returnToPool(buffer)

		bytes = bytes[:0]
	}
}

var bufSync sync.Mutex

func getBufferChanSize() int {
	bufferSize := time.Duration(atomic.LoadUint32(&jackBufferSize) / 2)
	sampleRate := time.Duration(Client.GetSampleRate())
	return int(bufferLen*sampleRate/bufferSize/time.Second) + 1
}

func getBuffer() (int, chan []jack.AudioSample) {
	bufSync.Lock()
	defer bufSync.Unlock()

	for i, bufp := range Buffers {
		buf := (*chan []jack.AudioSample)(atomic.LoadPointer(&bufp))
		if buf != nil {
			continue
		}
		buf2 := make(chan []jack.AudioSample, getBufferChanSize())
		atomic.StorePointer(&Buffers[i], unsafe.Pointer(&buf2))
		return i, buf2
	}
	return -1, nil
}

var jackBufferSize uint32

func initPool() {
	atomic.StoreUint32(&jackBufferSize, Client.GetBufferSize()*2)
	bufferSize := int(atomic.LoadUint32(&jackBufferSize))
	BufferPool = make(chan []jack.AudioSample, getBufferChanSize()*maxConns)
	for i := 0; i < cap(BufferPool); i++ {
		BufferPool <- make([]jack.AudioSample, bufferSize)
	}
}

func returnToPool(buffer []jack.AudioSample) {
	bufferSize := int(atomic.LoadUint32(&jackBufferSize))
	if cap(buffer) < bufferSize {
		BufferPool <- make([]jack.AudioSample, bufferSize)
	} else {
		BufferPool <- buffer[:bufferSize]
	}
}

func printStreamInfo() {
	fmt.Printf("Stream info: %dHz, ", Client.GetSampleRate())
	fmt.Printf("%dbit %s, ", bits, rawstreamer.EncodingString[formatFlag&rawstreamer.EncodingMask])
	fmt.Printf("%s, %v max buffer\n", endianness.String(), bufferLen)
}

func main() {
	ShuttingDown = make(chan struct{})

	if bits < 8 || bits > 32 || bits%8 != 0 {
		fmt.Println("Bit-depth must be one of: 8, 16, 24, 32")
		return
	}
	formatFlag = 0
	for flag, str := range rawstreamer.EncodingString {
		if format == str {
			formatFlag = flag
			break
		}
	}
	if formatFlag == 0 {
		fmt.Printf("Unsupported stream format: %s\n", format)
		return
	}
	if formatFlag == rawstreamer.EncodingFloatingPoint {
		bits = 32
	}

	if bigEndian {
		endianness = binary.BigEndian
		formatFlag |= rawstreamer.EncodingBigEndian
	} else {
		endianness = binary.LittleEndian
		formatFlag |= rawstreamer.EncodingLittleEndian
	}

	var err error
	bufferLen, err = time.ParseDuration(bufferStr)
	if err != nil {
		fmt.Printf("Invalid buffer length: %v\n", err)
		return
	}

	var status int
	Client, status = jack.ClientOpen("Raw Streamer", jack.NoStartServer)
	if status != 0 {
		fmt.Println("Status:", status)
		return
	}
	defer Client.Close()

	if code := Client.SetProcessCallback(process); code != 0 {
		fmt.Printf("Failed to set process callback: %d\n", code)
		return
	}
	if code := Client.SetSampleRateCallback(sampleRateChanged); code != 0 {
		fmt.Printf("Failed to set sample rate callback: %d\n", code)
		return
	}
	if code := Client.SetBufferSizeCallback(bufferSizeChanged); code != 0 {
		fmt.Printf("Failed to set buffer size callback: %d\n", code)
		return
	}
	if code := Client.SetPortRegistrationCallback(portRegistered); code != 0 {
		fmt.Printf("Failed to set port registration callback: %d\n", code)
		return
	}
	if code := Client.SetPortConnectCallback(portConnect); code != 0 {
		fmt.Printf("Failed to set port connect callback: %d\n", code)
		return
	}
	Client.OnShutdown(shutdown)

	if code := Client.Activate(); code != 0 {
		fmt.Printf("Failed to activate client: %d\n", code)
		return
	}

	for i := 0; i < 2; i++ {
		port := Client.PortRegister(fmt.Sprintf("in_%d", i), jack.DEFAULT_AUDIO_TYPE, jack.PortIsInput, 0)
		Ports = append(Ports, port)
	}
	Buffers = make([]unsafe.Pointer, maxConns)
	initPool()

	initPortMirror()
	updateProcs()
	updateSources()

	Listener, err = net.Listen("tcp", addr)
	if err != nil {
		fmt.Printf("Error listening on address '%s': %v\n", addr, err)
		return
	} else {
		fmt.Printf("Listening on address: %s\n", Listener.Addr().String())
	}
	for {
		conn, err := Listener.Accept()
		if err != nil {
			select {
			case <-ShuttingDown:
				ClientWaitGroup.Wait()
				return
			default:
				fmt.Printf("Error accepting connection: %v\n", err)
				continue
			}
		}
		ClientWaitGroup.Add(1)
		go streamConnection(conn.(*net.TCPConn))
	}
}

var formatFlag byte
var endianness binary.ByteOrder
var bufferLen time.Duration

var addr string
var maxConns int

var bits int
var format string
var bigEndian bool
var littleEndian bool

var bufferStr string

var mirror string
var procName string
var source string

func init() {
	flag.StringVar(&addr, "addr", ":5253", "Listen address")
	flag.IntVar(&maxConns, "max-conn", 128, "Maximum number of connected clients")

	flag.IntVar(&bits, "bits", 24, "Stream bit-depth")
	flag.StringVar(&format, "format", "int", "Stream format (int, uint, float)")
	flag.BoolVar(&bigEndian, "big-endian", false, "Big-endian stream encoding")
	flag.BoolVar(&littleEndian, "little-endian", true, "Little-endian stream encoding (default)")

	flag.StringVar(&bufferStr, "buffer", "100ms", "Max buffer length")

	flag.StringVar(&mirror, "mirror", "", "The name of a port to mirror (prefix matched)")
	flag.StringVar(&procName, "proc-name", "", "An alsa process to auto-connect (substring matched)")
	flag.StringVar(&source, "source", "", "The name of a port to auto-connect (prefix matched)")
	flag.Parse()
}
<commit_msg>Fix port register race<commit_after>package main

import (
	"encoding/binary"
	"flag"
	"fmt"
	"net"
	"os/exec"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"
	"unsafe"

	"github.com/xthexder/go-jack"
	"github.com/xthexder/rawstreamer"
)

var Client *jack.Client
var Ports []*jack.Port
var Buffers []unsafe.Pointer
var BufferPool chan []jack.AudioSample
var Listener net.Listener
var ClientWaitGroup sync.WaitGroup
var ShuttingDown chan struct{}

func process(nframes uint32) int {
	lsamples := Ports[0].GetBuffer(nframes)
	rsamples := Ports[1].GetBuffer(nframes)
	for client, bufp := range Buffers {
		buf := (*chan []jack.AudioSample)(atomic.LoadPointer(&bufp))
		if buf == nil {
			continue
		}

		n := 0
		for n < len(lsamples) {
			buffer := <-BufferPool
			nl := copy(buffer[:len(buffer)/2], lsamples[n:])
			buffer = buffer[:nl*2]
			copy(buffer[nl:], rsamples[n:])
			n += nl

			select {
			case *buf <- buffer:
			default:
				fmt.Println("Channel full for client:", client)
				n = len(lsamples)
			}
		}
	}
	return 0
}

func initPortMirror() {
	if len(mirror) <= 0 {
		return
	}

	ports := Client.GetPorts("^"+mirror, Ports[0].GetType(), jack.PortIsInput)
	for i, name := range ports {
		if i < len(Ports) {
			port := Client.GetPortByName(name)
			connections := port.GetConnections()
			for _, conn := range connections {
				Client.Connect(conn, Ports[i].GetName())
			}
		}
	}
}

var updateLock sync.Mutex

func updateProcs() {
	if len(procName) <= 0 {
		return
	}
	updateLock.Lock()
	defer updateLock.Unlock()

	ports := Client.GetPorts("^alsa-jack\\.jackP\\.", Ports[0].GetType(), jack.PortIsOutput)
	procs := make(map[string]int)
	for _, name := range ports {
		clientName := strings.SplitN(name, ":", 2)[0]
		pidStr := strings.SplitN(clientName[16:], ".", 2)[0]
		pid, err := strconv.Atoi(pidStr)
		if err != nil {
			continue
		}
		procs[clientName] = pid
	}
	for clientName, pid := range procs {
		out, err := exec.Command("ps", "-p", strconv.Itoa(pid), "-o", "comm=").Output()
		if err != nil {
			fmt.Println("Error finding process:", err)
			return
		}
		proc := string(out)
		if strings.Contains(proc, procName) {
			ports = Client.GetPorts("^"+clientName+":", Ports[0].GetType(), jack.PortIsOutput)
			for i, name := range ports {
				if i < len(Ports) {
					Client.Connect(name, Ports[i].GetName())
				}
			}
		}
	}
}

func updateSources() {
	if len(source) <= 0 {
		return
	}
	updateLock.Lock()
	defer updateLock.Unlock()

	ports := Client.GetPorts("^"+source, Ports[0].GetType(), jack.PortIsOutput)
	sources := make(map[string][]string)
	for _, name := range ports {
		clientName := strings.SplitN(name, ":", 2)[0]
		sources[clientName] = append(sources[clientName], name)
	}
	for _, names := range sources {
		if len(names) == 1 { // Mono source
			for _, port := range Ports {
				Client.Connect(names[0], port.GetName())
			}
		} else {
			for i, name := range names {
				if i < len(Ports) {
					Client.Connect(name, Ports[i].GetName())
				}
			}
		}
	}
}

func portRegistered(portId jack.PortId, registered bool) {
	if registered && !Client.IsPortMine(Client.GetPortById(portId)) {
		go updateProcs()
		go updateSources()
	}
}

func disconnectClients() {
	for _, bufp := range Buffers {
		buf := (*chan []jack.AudioSample)(atomic.LoadPointer(&bufp))
		if buf == nil {
			continue
		}
		close(*buf)
	}
}

func sampleRateChanged(sampleRate uint32) int {
	printStreamInfo()
	disconnectClients()
	return 0
}

func bufferSizeChanged(bufferSize uint32) int {
	atomic.StoreUint32(&jackBufferSize, bufferSize*2)
	return 0
}

func portConnect(portAId, portBId jack.PortId, connected bool) {
	portA := Client.GetPortById(portAId)
	portB := Client.GetPortById(portBId)
	if Client.IsPortMine(portB) {
		return
	}

	if len(mirror) > 0 && strings.HasPrefix(portB.GetName(), mirror) {
		clientName := portB.GetClientName()
		go func() {
			ports := Client.GetPorts("^"+clientName+":", Ports[0].GetType(), jack.PortIsInput)
			for i, name := range ports {
				if name == portB.GetName() && i < len(Ports) {
					if connected {
						Client.ConnectPorts(portA, Ports[i])
					} else {
						Client.DisconnectPorts(portA, Ports[i])
					}
					break
				}
			}
		}()
	}
}

func shutdown() {
	fmt.Println("Shutting down")
	close(ShuttingDown)
	Listener.Close()
	disconnectClients()
}

var ErrWriteTimeout = fmt.Errorf("write timeout")

func writeAligned(conn *net.TCPConn, buf []byte, timeout time.Time) error {
	conn.SetWriteDeadline(timeout)
	n, err := conn.Write(buf)
	if err != nil {
		if err2, ok := err.(*net.OpError); ok && err2.Timeout() {
			// Add extra padding to make sure we're still aligned
			align := n % (bits / 4)
			if align > 0 {
				fmt.Printf("Realigning: %d + %d\n", n, (bits/4)-align)
				conn.SetWriteDeadline(time.Time{})
				_, err = conn.Write(make([]byte, (bits/4)-align))
				if err != nil {
					return err
				}
			}
			return ErrWriteTimeout
		}
	}
	return err
}

func streamConnection(conn *net.TCPConn) {
	defer conn.Close()
	defer ClientWaitGroup.Done()
	bufi, buf := getBuffer()
	if buf == nil {
		conn.Write([]byte{'R', 0})
		conn.Close()
		return
	}

	defer atomic.StorePointer(&Buffers[bufi], nil)

	numBytes := bits / 8
	_, err := conn.Write([]byte{'R', 1, formatFlag, byte(numBytes)})
	if err != nil {
		return
	}
	bytes := make([]byte, 4)
	sampleRate := Client.GetSampleRate()
	endianness.PutUint32(bytes, sampleRate)
	_, err = conn.Write(bytes[:4])
	if err != nil {
		return
	}

	sample := make([]byte, bits/4)
	align := 8 % len(sample)
	if align > 0 {
		// Add extra padding to make the header an even number of samples in length
		_, err = conn.Write(sample[:len(sample)-align])
		if err != nil {
			return
		}
	}

	bytes = make([]byte, 0, int(Client.GetBufferSize())*2)
	for {
		buffer := <-buf
		if buffer == nil {
			return
		}

		lsamples := buffer[:len(buffer)/2]
		rsamples := buffer[len(buffer)/2:]

		for i := range lsamples {
			rawstreamer.WriteFloat32(sample[:numBytes], float32(lsamples[i]), formatFlag, endianness)
			rawstreamer.WriteFloat32(sample[numBytes:], float32(rsamples[i]), formatFlag, endianness)

			bytes = append(bytes, sample...)
		}

		err = writeAligned(conn, bytes, time.Now().Add(bufferLen))
		if err == ErrWriteTimeout {
			fmt.Println("Write timeout!")
		} else if err != nil {
			fmt.Printf("Sample write error: %v\n", err)
			returnToPool(buffer)
			return
		}

		returnToPool(buffer)

		bytes = bytes[:0]
	}
}

var bufSync sync.Mutex

func getBufferChanSize() int {
	bufferSize := time.Duration(atomic.LoadUint32(&jackBufferSize) / 2)
	sampleRate := time.Duration(Client.GetSampleRate())
	return int(bufferLen*sampleRate/bufferSize/time.Second) + 1
}

func getBuffer() (int, chan []jack.AudioSample) {
	bufSync.Lock()
	defer bufSync.Unlock()

	for i, bufp := range Buffers {
		buf := (*chan []jack.AudioSample)(atomic.LoadPointer(&bufp))
		if buf != nil {
			continue
		}
		buf2 := make(chan []jack.AudioSample, getBufferChanSize())
		atomic.StorePointer(&Buffers[i], unsafe.Pointer(&buf2))
		return i, buf2
	}
	return -1, nil
}

var jackBufferSize uint32

func initPool() {
	atomic.StoreUint32(&jackBufferSize, Client.GetBufferSize()*2)
	bufferSize := int(atomic.LoadUint32(&jackBufferSize))
	BufferPool = make(chan []jack.AudioSample, getBufferChanSize()*maxConns)
	for i := 0; i < cap(BufferPool); i++ {
		BufferPool <- make([]jack.AudioSample, bufferSize)
	}
}

func returnToPool(buffer []jack.AudioSample) {
	bufferSize := int(atomic.LoadUint32(&jackBufferSize))
	if cap(buffer) < bufferSize {
		BufferPool <- make([]jack.AudioSample, bufferSize)
	} else {
		BufferPool <- buffer[:bufferSize]
	}
}

func printStreamInfo() {
	fmt.Printf("Stream info: %dHz, ", Client.GetSampleRate())
	fmt.Printf("%dbit %s, ", bits, rawstreamer.EncodingString[formatFlag&rawstreamer.EncodingMask])
	fmt.Printf("%s, %v max buffer\n", endianness.String(), bufferLen)
}

func main() {
	ShuttingDown = make(chan struct{})

	if bits < 8 || bits > 32 || bits%8 != 0 {
		fmt.Println("Bit-depth must be one of: 8, 16, 24, 32")
		return
	}
	formatFlag = 0
	for flag, str := range rawstreamer.EncodingString {
		if format == str {
			formatFlag = flag
			break
		}
	}
	if formatFlag == 0 {
		fmt.Printf("Unsupported stream format: %s\n", format)
		return
	}
	if formatFlag == rawstreamer.EncodingFloatingPoint {
		bits = 32
	}

	if bigEndian {
		endianness = binary.BigEndian
		formatFlag |= rawstreamer.EncodingBigEndian
	} else {
		endianness = binary.LittleEndian
		formatFlag |= rawstreamer.EncodingLittleEndian
	}

	var err error
	bufferLen, err = time.ParseDuration(bufferStr)
	if err != nil {
		fmt.Printf("Invalid buffer length: %v\n", err)
		return
	}

	var status int
	Client, status = jack.ClientOpen("Raw Streamer", jack.NoStartServer)
	if status != 0 {
		fmt.Println("Status:", status)
		return
	}
	defer Client.Close()

	if code := Client.SetProcessCallback(process); code != 0 {
		fmt.Printf("Failed to set process callback: %d\n", code)
		return
	}
	if code := Client.SetSampleRateCallback(sampleRateChanged); code != 0 {
		fmt.Printf("Failed to set sample rate callback: %d\n", code)
		return
	}
	if code := Client.SetBufferSizeCallback(bufferSizeChanged); code != 0 {
		fmt.Printf("Failed to set buffer size callback: %d\n", code)
		return
	}
	if code := Client.SetPortRegistrationCallback(portRegistered); code != 0 {
		fmt.Printf("Failed to set port registration callback: %d\n", code)
		return
	}
	if code := Client.SetPortConnectCallback(portConnect); code != 0 {
		fmt.Printf("Failed to set port connect callback: %d\n", code)
		return
	}
	Client.OnShutdown(shutdown)

	if code := Client.Activate(); code != 0 {
		fmt.Printf("Failed to activate client: %d\n", code)
		return
	}

	for i := 0; i < 2; i++ {
		port := Client.PortRegister(fmt.Sprintf("in_%d", i), jack.DEFAULT_AUDIO_TYPE, jack.PortIsInput, 0)
		Ports = append(Ports, port)
	}
	Buffers = make([]unsafe.Pointer, maxConns)
	initPool()

	initPortMirror()
	updateProcs()
	updateSources()

	Listener, err = net.Listen("tcp", addr)
	if err != nil {
		fmt.Printf("Error listening on address '%s': %v\n", addr, err)
		return
	} else {
		fmt.Printf("Listening on address: %s\n", Listener.Addr().String())
	}
	for {
		conn, err := Listener.Accept()
		if err != nil {
			select {
			case <-ShuttingDown:
				ClientWaitGroup.Wait()
				return
			default:
				fmt.Printf("Error accepting connection: %v\n", err)
				continue
			}
		}
		ClientWaitGroup.Add(1)
		go streamConnection(conn.(*net.TCPConn))
	}
}

var formatFlag byte
var endianness binary.ByteOrder
var bufferLen time.Duration

var addr string
var maxConns int

var bits int
var format string
var bigEndian bool
var littleEndian bool

var bufferStr string

var mirror string
var procName string
var source string

func init() {
	flag.StringVar(&addr, "addr", ":5253", "Listen address")
	flag.IntVar(&maxConns, "max-conn", 128, "Maximum number of connected clients")

	flag.IntVar(&bits, "bits", 24, "Stream bit-depth")
	flag.StringVar(&format, "format", "int", "Stream format (int, uint, float)")
	flag.BoolVar(&bigEndian, "big-endian", false, "Big-endian stream encoding")
	flag.BoolVar(&littleEndian, "little-endian", true, "Little-endian stream encoding (default)")

	flag.StringVar(&bufferStr, "buffer", "100ms", "Max buffer length")

	flag.StringVar(&mirror, "mirror", "", "The name of a port to mirror (prefix matched)")
	flag.StringVar(&procName, "proc-name", "", "An alsa process to auto-connect (substring matched)")
	flag.StringVar(&source, "source", "", "The name of a port to auto-connect (prefix matched)")
	flag.Parse()
}
<|endoftext|>


<commit_before>package streamtools

import (
	"encoding/xml"
	"github.com/bitly/go-simplejson"
	"github.com/bmizerany/aws4"
	"io/ioutil"
	"log"
	"net/url"
	"os"
	"time"
)

var (
	AWSAccessKeyId      string = os.Getenv("AWS_ACCESS_KEY_ID")
	AWSAccessSecret     string = os.Getenv("AWS_SECRET_ACCESS_KEY")
	AWSSQSAPIVersion    string = "2012-11-05"
	AWSSignatureVersion string = "4"
)

type Message struct {
	// this is a list in case I'm ever brave enough to up the "MaxNumberOfMessages" away from 1
	Body          []string `xml:"ReceiveMessageResult>Message>Body"`
	ReceiptHandle []string `xml:"ReceiveMessageResult>Message>ReceiptHandle"`
}

func PollSQS(SQSEndpoint string) Message {
	query := make(url.Values)
	query.Add("Action", "ReceiveMessage")
	query.Add("AttributeName", "All")
	query.Add("Version", AWSSQSAPIVersion)
	query.Add("SignatureVersion", AWSSignatureVersion)
	query.Add("WaitTimeSeconds", "10")

	keys := &aws4.Keys{
		AccessKey: AWSAccessKeyId,
		SecretKey: AWSAccessSecret,
	}

	c := aws4.Client{Keys: keys}

	log.Println("querying", SQSEndpoint+query.Encode())

	resp, err := c.Get(SQSEndpoint + query.Encode())
	if err != nil {
		log.Fatal(err.Error())
	}

	var v Message

	body, err := ioutil.ReadAll(resp.Body)
	err = xml.Unmarshal(body, &v)
	if err != nil {
		log.Println(err.Error())
	}
	return v

}

func deleteMessage(SQSEndpoint string, ReceiptHandle string) {
	query := make(url.Values)
	query.Add("Action", "DeleteMessage")
	query.Add("ReceiptHandle", ReceiptHandle)
	query.Add("Version", AWSSQSAPIVersion)
	query.Add("SignatureVersion", AWSSignatureVersion)

	keys := &aws4.Keys{
		AccessKey: AWSAccessKeyId,
		SecretKey: AWSAccessSecret,
	}

	c := aws4.Client{Keys: keys}

	log.Println("querying", SQSEndpoint+query.Encode())

	_, err := c.Get(SQSEndpoint + query.Encode())
	if err != nil {
		log.Fatal(err.Error())
	}
}

func FromSQS(outChan chan *simplejson.Json, ruleChan chan *simplejson.Json) {

	rules := <-ruleChan

	SQSEndpoint, err := rules.Get("SQSEndpoint").String()
	if err != nil {
		log.Fatal(err.Error())
	}
	log.Println("Listening to", SQSEndpoint)

	timer := time.NewTimer(1)

	for {
		select {
		case <-ruleChan:
		case <-timer.C:
			m := PollSQS(SQSEndpoint)
			if len(m.Body) > 0 {
				for i, body := range m.Body {
					out, err := simplejson.NewJson([]byte(body))
					if err != nil {
						log.Fatal(err.Error())
					}
					outChan <- out
					deleteMessage(SQSEndpoint, m.ReceiptHandle[i])
				}
				timer.Reset(time.Duration(10) * time.Millisecond)
			} else {
				log.Println("waiting 10 seconds")
				timer.Reset(time.Duration(10) * time.Second)
			}

		}

	}

}
<commit_msg>first in probably a large amount of better logging<commit_after>package streamtools

import (
	"encoding/xml"
	"github.com/bitly/go-simplejson"
	"github.com/bmizerany/aws4"
	"io/ioutil"
	"log"
	"net/url"
	"os"
	"time"
)

var (
	AWSAccessKeyId      string = os.Getenv("AWS_ACCESS_KEY_ID")
	AWSAccessSecret     string = os.Getenv("AWS_SECRET_ACCESS_KEY")
	AWSSQSAPIVersion    string = "2012-11-05"
	AWSSignatureVersion string = "4"
)

type Message struct {
	// this is a list in case I'm ever brave enough to up the "MaxNumberOfMessages" away from 1
	Body          []string `xml:"ReceiveMessageResult>Message>Body"`
	ReceiptHandle []string `xml:"ReceiveMessageResult>Message>ReceiptHandle"`
}

func PollSQS(SQSEndpoint string) Message {
	query := make(url.Values)
	query.Add("Action", "ReceiveMessage")
	query.Add("AttributeName", "All")
	query.Add("Version", AWSSQSAPIVersion)
	query.Add("SignatureVersion", AWSSignatureVersion)
	query.Add("WaitTimeSeconds", "10")

	keys := &aws4.Keys{
		AccessKey: AWSAccessKeyId,
		SecretKey: AWSAccessSecret,
	}

	c := aws4.Client{Keys: keys}

	log.Println("querying", SQSEndpoint+query.Encode())

	resp, err := c.Get(SQSEndpoint + query.Encode())
	if err != nil {
		log.Fatal(err.Error())
	}

	var v Message

	body, err := ioutil.ReadAll(resp.Body)
	err = xml.Unmarshal(body, &v)
	if err != nil {
		log.Println(err.Error())
	}
	return v

}

func deleteMessage(SQSEndpoint string, ReceiptHandle string) {
	query := make(url.Values)
	query.Add("Action", "DeleteMessage")
	query.Add("ReceiptHandle", ReceiptHandle)
	query.Add("Version", AWSSQSAPIVersion)
	query.Add("SignatureVersion", AWSSignatureVersion)

	keys := &aws4.Keys{
		AccessKey: AWSAccessKeyId,
		SecretKey: AWSAccessSecret,
	}

	c := aws4.Client{Keys: keys}

	log.Println("querying", SQSEndpoint+query.Encode())

	_, err := c.Get(SQSEndpoint + query.Encode())
	if err != nil {
		log.Fatal(err.Error())
	}
}

func FromSQS(outChan chan *simplejson.Json, ruleChan chan *simplejson.Json) {

	log.Println("[FROMSQS] AccessKey:", AWSAccessKeyId)
	log.Println("[FROMSQS] AccessSecret:", AWSAccessSecret)

	rules := <-ruleChan

	SQSEndpoint, err := rules.Get("SQSEndpoint").String()
	if err != nil {
		log.Fatal(err.Error())
	}
	log.Println("[FROMSQS] Listening to", SQSEndpoint)

	timer := time.NewTimer(1)

	for {
		select {
		case <-ruleChan:
		case <-timer.C:
			m := PollSQS(SQSEndpoint)
			if len(m.Body) > 0 {
				for i, body := range m.Body {
					out, err := simplejson.NewJson([]byte(body))
					if err != nil {
						log.Fatal(err.Error())
					}
					outChan <- out
					deleteMessage(SQSEndpoint, m.ReceiptHandle[i])
				}
				timer.Reset(time.Duration(10) * time.Millisecond)
			} else {
				log.Println("[FROMSQS] waiting 10 seconds")
				timer.Reset(time.Duration(10) * time.Second)
			}

		}

	}

}
<|endoftext|>


<commit_before>package shard

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"sync"

	"github.com/unigraph/rdb"
)

var ShardNameFn = func(i uint) string { return fmt.Sprintf("%03d", i) }

type Shard struct {
	dbs []*rdb.DB
}

func Open(opts *rdb.Options, name string, shardsNum uint) (*Shard, error) {
	if err := checkValid(name, shardsNum); err != nil {
		return nil, err
	}
	s := &Shard{}
	for i := uint(0); i < shardsNum; i++ {
		sName := filepath.Join(name, ShardNameFn(i))
		db, err := rdb.OpenDb(opts, sName)
		if err != nil {
			s.Close()
			return nil, err
		}
		s.dbs = append(s.dbs, db)
	}
	return s, nil
}

func OpenForReadOnly(opts *rdb.Options, name string, shardsNum uint, errorIfLogFileExist bool) (*Shard, error) {
	if err := checkValid(name, shardsNum); err != nil {
		return nil, err
	}
	s := &Shard{}
	for i := uint(0); i < shardsNum; i++ {
		sName := filepath.Join(name, ShardNameFn(i))
		db, err := rdb.OpenDbForReadOnly(opts, sName, errorIfLogFileExist)
		if err != nil {
			s.Close()
			return nil, err
		}
		s.dbs = append(s.dbs, db)
	}
	return s, nil
}

type errors []error

func (e errors) Error() string {
	res := ""
	for _, err := range e {
		res += err.Error()
	}
	return res
}

func (s *Shard) Flush(opts *rdb.FlushOptions) error {
	wg := sync.WaitGroup{}
	wg.Add(len(s.dbs))
	err := errors(nil)
	l := sync.RWMutex{}
	for _, db := range s.dbs {
		go func(db *rdb.DB) {
			if e := db.Flush(opts); e != nil {
				l.Lock()
				err = append(err, e)
				l.Unlock()
			}
			wg.Done()
		}(db)
	}
	wg.Wait()
	if len(err) == 0 {
		return nil
	}
	return err
}

func (s *Shard) CompactRange(r rdb.Range) {
	wg := sync.WaitGroup{}
	wg.Add(len(s.dbs))
	for _, db := range s.dbs {
		go func(db *rdb.DB) {
			db.CompactRange(r)
			wg.Done()
		}(db)
	}
	wg.Wait()
}

func (s *Shard) DBs() []*rdb.DB {
	return append([]*rdb.DB(nil), s.dbs...)
}

func (s *Shard) Close() {
	wg := sync.WaitGroup{}
	wg.Add(len(s.dbs))
	for _, db := range s.dbs {
		go func(db *rdb.DB) {
			db.Close()
			wg.Done()
		}(db)
	}
	wg.Wait()
}

func GetShardNum(name string) uint {
	if files, err := ioutil.ReadDir(name); os.IsNotExist(err) {
		return 0
	} else {
		shards := map[string]bool{}
		for _, file := range files {
			shards[file.Name()] = true
		}
		i := 0
		for shards[ShardNameFn(uint(i))] {
			i++
		}
		if len(shards) != i {
			return 0
		}
		return uint(i)
	}
}

func checkValid(name string, shardsNum uint) error {
	if shardsNum == 0 || shardsNum > 999 {
		return fmt.Errorf("Number of shards has to be bigger than 0 and lower than 1000")
	}
	files, err := ioutil.ReadDir(name)
	if os.IsNotExist(err) { // does not exists, let's create empty
		return os.Mkdir(name, 0700)
	} else if err != nil { // some other error related to ReadDir
		return err
	} else { // exists, let's check the content
		shards := map[string]bool{}
		for _, file := range files {
			if matched, _ := regexp.MatchString(`\d{3}`, file.Name()); matched {
				shards[file.Name()] = true
			}
		}
		if len(shards) != 0 {
			if uint(len(shards)) != shardsNum {
				return fmt.Errorf("Wrong number of shards provided (%v)", len(shards))
			}
			for i := uint(0); i < shardsNum; i++ {
				sName := ShardNameFn(i)
				if !shards[sName] {
					return fmt.Errorf("Wrong number of shards provided (%v)", len(shards))
				}
			}
		}
	}
	return nil
}
<commit_msg>message update<commit_after>package shard

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"sync"

	"github.com/unigraph/rdb"
)

var ShardNameFn = func(i uint) string { return fmt.Sprintf("%03d", i) }

type Shard struct {
	dbs []*rdb.DB
}

func Open(opts *rdb.Options, name string, shardsNum uint) (*Shard, error) {
	if err := checkValid(name, shardsNum); err != nil {
		return nil, err
	}
	s := &Shard{}
	for i := uint(0); i < shardsNum; i++ {
		sName := filepath.Join(name, ShardNameFn(i))
		db, err := rdb.OpenDb(opts, sName)
		if err != nil {
			s.Close()
			return nil, err
		}
		s.dbs = append(s.dbs, db)
	}
	return s, nil
}

func OpenForReadOnly(opts *rdb.Options, name string, shardsNum uint, errorIfLogFileExist bool) (*Shard, error) {
	if err := checkValid(name, shardsNum); err != nil {
		return nil, err
	}
	s := &Shard{}
	for i := uint(0); i < shardsNum; i++ {
		sName := filepath.Join(name, ShardNameFn(i))
		db, err := rdb.OpenDbForReadOnly(opts, sName, errorIfLogFileExist)
		if err != nil {
			s.Close()
			return nil, err
		}
		s.dbs = append(s.dbs, db)
	}
	return s, nil
}

type errors []error

func (e errors) Error() string {
	res := ""
	for _, err := range e {
		res += err.Error()
	}
	return res
}

func (s *Shard) Flush(opts *rdb.FlushOptions) error {
	wg := sync.WaitGroup{}
	wg.Add(len(s.dbs))
	err := errors(nil)
	l := sync.RWMutex{}
	for _, db := range s.dbs {
		go func(db *rdb.DB) {
			if e := db.Flush(opts); e != nil {
				l.Lock()
				err = append(err, e)
				l.Unlock()
			}
			wg.Done()
		}(db)
	}
	wg.Wait()
	if len(err) == 0 {
		return nil
	}
	return err
}

func (s *Shard) CompactRange(r rdb.Range) {
	wg := sync.WaitGroup{}
	wg.Add(len(s.dbs))
	for _, db := range s.dbs {
		go func(db *rdb.DB) {
			db.CompactRange(r)
			wg.Done()
		}(db)
	}
	wg.Wait()
}

func (s *Shard) DBs() []*rdb.DB {
	return append([]*rdb.DB(nil), s.dbs...)
}

func (s *Shard) Close() {
	wg := sync.WaitGroup{}
	wg.Add(len(s.dbs))
	for _, db := range s.dbs {
		go func(db *rdb.DB) {
			db.Close()
			wg.Done()
		}(db)
	}
	wg.Wait()
}

func GetShardNum(name string) uint {
	if files, err := ioutil.ReadDir(name); os.IsNotExist(err) {
		return 0
	} else {
		shards := map[string]bool{}
		for _, file := range files {
			shards[file.Name()] = true
		}
		i := 0
		for shards[ShardNameFn(uint(i))] {
			i++
		}
		if len(shards) != i {
			return 0
		}
		return uint(i)
	}
}

func checkValid(name string, shardsNum uint) error {
	if shardsNum == 0 || shardsNum > 999 {
		return fmt.Errorf("Number of shards has to be bigger than 0 and lower than 1000")
	}
	files, err := ioutil.ReadDir(name)
	if os.IsNotExist(err) { // does not exists, let's create empty
		return os.Mkdir(name, 0700)
	} else if err != nil { // some other error related to ReadDir
		return err
	} else { // exists, let's check the content
		shards := map[string]bool{}
		for _, file := range files {
			if matched, _ := regexp.MatchString(`\d{3}`, file.Name()); matched {
				shards[file.Name()] = true
			}
		}
		if len(shards) != 0 {
			if uint(len(shards)) != shardsNum {
				return fmt.Errorf("Wrong number of shards provided (found %v)", len(shards))
			}
			for i := uint(0); i < shardsNum; i++ {
				sName := ShardNameFn(i)
				if !shards[sName] {
					return fmt.Errorf("Wrong number of shards provided (found %v)", len(shards))
				}
			}
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package git

import (
	"bytes"
	"errors"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"golang.org/x/net/context"
)

const (
	// GitCommandDateLayout corresponds to `git log --date=format:%FT%T%z` date format.
	GitCommandDateLayout = "2006-01-02T15:04:05-0700"

	gitPrettyFormat = "%H\n%an\n%cn\n%cd\n%s"
	gitDateFormat   = "format:%FT%T%z"
)

var (
	gitPrettyFormatFieldsNum = strings.Count(gitPrettyFormat, "\n") + 1
	errUnexpectedExit        = errors.New("unexpected exit")
)

type systemGit string

// SystemGit returns an object that wraps system git command and implements git.Command interface.
// If git is not found in PATH an error will be returned.
func SystemGit() (systemGit, error) {
	cmd, err := exec.LookPath("git")
	if err != nil {
		return systemGit(""), errors.New("git is not found in PATH")
	}

	return systemGit(cmd), nil
}

// Exec runs specified Git command in path and returns its output. If Git returns a non-zero
// status, an error is returned and output contains error details.
func (gitCmd systemGit) Exec(ctx context.Context, path, command string, args ...string) (output []byte, err error) {
	cmd := exec.CommandContext(ctx, string(gitCmd), append([]string{command}, args...)...)
	cmd.Dir = path

	output, err = cmd.Output()
	if err != nil {
		if exitErr, ok := err.(*exec.ExitError); ok {
			err = errors.New(string(exitErr.Stderr))
		} else {
			err = errUnexpectedExit
		}

		return nil, err
	}

	return bytes.TrimSpace(output), nil
}

// IsRepository checks if there if `path` is a git repository.
func (gitCmd systemGit) IsRepository(ctx context.Context, path string) bool {
	if fileInfo, err := os.Stat(path); err != nil {
		if os.IsNotExist(err) {
			return false
		}

		log.Printf("[WARN] failed to stat %s (%s)", path, err)
		return false
	} else if !fileInfo.IsDir() {
		return false
	}

	output, err := gitCmd.Exec(ctx, path, "rev-parse", "--is-inside-git-dir")
	if err == errUnexpectedExit {
		log.Printf("[WARN] git rev-parse --is-inside-git-dir returned %s for %s (%s)", err, path, string(output))
	} else if err != nil {
		return false
	}

	switch string(output) {
	case "true":
		return true
	case "false":
		return false
	default:
		log.Printf("[WARN] git rev-parse --is-inside-git-dir returned unexpected output for %s: %q", path, string(output))
		return false
	}
}

// CurrentBranch returns the name of current branch in `path`.
func (gitCmd systemGit) CurrentBranch(ctx context.Context, path string) string {
	refName, err := gitCmd.Exec(ctx, path, "symbolic-ref", "HEAD")
	if err != nil {
		log.Printf("[WARN] git symbolic-ref HEAD returned %s for %s (%s)", err, path, string(refName))
		return DefaultMaster
	}

	if !bytes.HasPrefix(refName, []byte("refs/heads/")) {
		log.Printf("[WARN] unexpected reference name for %s (%q)", path, refName)
		return DefaultMaster
	}

	return string(bytes.TrimPrefix(refName, []byte("refs/heads/")))
}

// LastCommit returns the latest commit from `path`.
func (gitCmd systemGit) LastCommit(ctx context.Context, path string) (commit Commit, err error) {
	output, err := gitCmd.Exec(ctx, path, "log", "-n", "1", "--pretty="+gitPrettyFormat, "--date="+gitDateFormat)
	if err != nil {
		log.Printf("[WARN] git log returned error %s for %s (%s)", err, path, string(output))
		return commit, nil
	}

	lines := strings.SplitN(string(output), "\n", gitPrettyFormatFieldsNum)
	if len(lines) < gitPrettyFormatFieldsNum {
		log.Printf("[WARN] unexpected output from git log for %s (%s)", path, string(output))
		return commit, nil
	}

	commit.SHA, commit.Author, commit.Committer, commit.Message = lines[0], lines[1], lines[2], lines[4]
	commit.Date, err = time.Parse(GitCommandDateLayout, lines[3])
	if err != nil {
		log.Printf("[WARN] unexpected date format from git log for %s (%s)", path, lines[3])
		commit.Date = time.Time{}
	}

	return commit, nil
}

// CloneMirror performs mirror clone of specified git URL to `path`.
func (gitCmd systemGit) CloneMirror(ctx context.Context, gitURL, path string) error {
	dir, projectName := filepath.Dir(path), filepath.Base(path)

	if err := os.MkdirAll(dir, 0755); err != nil {
		log.Printf("failed to create %s (%s)", dir, err)
		return fmt.Errorf("failed to clone %s to %s", gitURL, path)
	}

	output, err := gitCmd.Exec(ctx, dir, "clone", "--mirror", gitURL, projectName)
	if err != nil {
		log.Printf("git clone --mirror %s to %s returned %s (%s)", gitURL, path, err, string(output))
		return fmt.Errorf("failed to clone %s to %s", gitURL, path)
	}

	return nil
}

// UpdateRemote does `git remote update` in specified `path`.
func (gitCmd systemGit) UpdateRemote(ctx context.Context, path string) error {
	output, err := gitCmd.Exec(ctx, path, "remote", "update")
	if err != nil {
		log.Printf("[WARN] git remote update returned %s for %s (%s)", err, path, string(output))
		return errors.New("update failed")
	}

	return nil
}
<commit_msg>Do not export (git.systemGit).Exec()<commit_after>package git

import (
	"bytes"
	"errors"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"golang.org/x/net/context"
)

const (
	// GitCommandDateLayout corresponds to `git log --date=format:%FT%T%z` date format.
	GitCommandDateLayout = "2006-01-02T15:04:05-0700"

	gitPrettyFormat = "%H\n%an\n%cn\n%cd\n%s"
	gitDateFormat   = "format:%FT%T%z"
)

var (
	gitPrettyFormatFieldsNum = strings.Count(gitPrettyFormat, "\n") + 1
	errUnexpectedExit        = errors.New("unexpected exit")
)

type systemGit string

// SystemGit returns an object that wraps system git command and implements git.Command interface.
// If git is not found in PATH an error will be returned.
func SystemGit() (systemGit, error) {
	cmd, err := exec.LookPath("git")
	if err != nil {
		return systemGit(""), errors.New("git is not found in PATH")
	}

	return systemGit(cmd), nil
}

// IsRepository checks if there if `path` is a git repository.
func (gitCmd systemGit) IsRepository(ctx context.Context, path string) bool {
	if fileInfo, err := os.Stat(path); err != nil {
		if os.IsNotExist(err) {
			return false
		}

		log.Printf("[WARN] failed to stat %s (%s)", path, err)
		return false
	} else if !fileInfo.IsDir() {
		return false
	}

	output, err := gitCmd.exec(ctx, path, "rev-parse", "--is-inside-git-dir")
	if err == errUnexpectedExit {
		log.Printf("[WARN] git rev-parse --is-inside-git-dir returned %s for %s (%s)", err, path, string(output))
	} else if err != nil {
		return false
	}

	switch string(output) {
	case "true":
		return true
	case "false":
		return false
	default:
		log.Printf("[WARN] git rev-parse --is-inside-git-dir returned unexpected output for %s: %q", path, string(output))
		return false
	}
}

// CurrentBranch returns the name of current branch in `path`.
func (gitCmd systemGit) CurrentBranch(ctx context.Context, path string) string {
	refName, err := gitCmd.exec(ctx, path, "symbolic-ref", "HEAD")
	if err != nil {
		log.Printf("[WARN] git symbolic-ref HEAD returned %s for %s (%s)", err, path, string(refName))
		return DefaultMaster
	}

	if !bytes.HasPrefix(refName, []byte("refs/heads/")) {
		log.Printf("[WARN] unexpected reference name for %s (%q)", path, refName)
		return DefaultMaster
	}

	return string(bytes.TrimPrefix(refName, []byte("refs/heads/")))
}

// LastCommit returns the latest commit from `path`.
func (gitCmd systemGit) LastCommit(ctx context.Context, path string) (commit Commit, err error) {
	output, err := gitCmd.exec(ctx, path, "log", "-n", "1", "--pretty="+gitPrettyFormat, "--date="+gitDateFormat)
	if err != nil {
		log.Printf("[WARN] git log returned error %s for %s (%s)", err, path, string(output))
		return commit, nil
	}

	lines := strings.SplitN(string(output), "\n", gitPrettyFormatFieldsNum)
	if len(lines) < gitPrettyFormatFieldsNum {
		log.Printf("[WARN] unexpected output from git log for %s (%s)", path, string(output))
		return commit, nil
	}

	commit.SHA, commit.Author, commit.Committer, commit.Message = lines[0], lines[1], lines[2], lines[4]
	commit.Date, err = time.Parse(GitCommandDateLayout, lines[3])
	if err != nil {
		log.Printf("[WARN] unexpected date format from git log for %s (%s)", path, lines[3])
		commit.Date = time.Time{}
	}

	return commit, nil
}

// CloneMirror performs mirror clone of specified git URL to `path`.
func (gitCmd systemGit) CloneMirror(ctx context.Context, gitURL, path string) error {
	dir, projectName := filepath.Dir(path), filepath.Base(path)

	if err := os.MkdirAll(dir, 0755); err != nil {
		log.Printf("failed to create %s (%s)", dir, err)
		return fmt.Errorf("failed to clone %s to %s", gitURL, path)
	}

	output, err := gitCmd.exec(ctx, dir, "clone", "--mirror", gitURL, projectName)
	if err != nil {
		log.Printf("git clone --mirror %s to %s returned %s (%s)", gitURL, path, err, string(output))
		return fmt.Errorf("failed to clone %s to %s", gitURL, path)
	}

	return nil
}

// UpdateRemote does `git remote update` in specified `path`.
func (gitCmd systemGit) UpdateRemote(ctx context.Context, path string) error {
	output, err := gitCmd.exec(ctx, path, "remote", "update")
	if err != nil {
		log.Printf("[WARN] git remote update returned %s for %s (%s)", err, path, string(output))
		return errors.New("update failed")
	}

	return nil
}

func (gitCmd systemGit) exec(ctx context.Context, path, command string, args ...string) (output []byte, err error) {
	cmd := exec.CommandContext(ctx, string(gitCmd), append([]string{command}, args...)...)
	cmd.Dir = path

	output, err = cmd.Output()
	if err != nil {
		if exitErr, ok := err.(*exec.ExitError); ok {
			err = errors.New(string(exitErr.Stderr))
		} else {
			err = errUnexpectedExit
		}

		return nil, err
	}

	return bytes.TrimSpace(output), nil
}
<|endoftext|>


<commit_before>package framework

import (
	"flag"
	"fmt"
	"log"
	"reflect"
	"strings"
	"testing"

	"github.com/hashicorp/nomad/e2e/framework/provisioning"
)

const frameworkHelp = `
Usage: go test -v ./e2e [options]

These flags are coarse overrides for the test environment.

  -forceRun    skip all environment checks when filtering test suites
  -local       force default no-op provisioning
  -skipTests   skips all tests and only provisions
  -slow        include execution of slow test suites
  -suite       run specified test suite
  -showHelp    shows this help text

Provisioning flags tell the test runner to pre-provision the cluster before
running all tests. These flags can be passed to 'go test'. If no
'-provision.*' flag is set, the test runner assumes the cluster has already
been configured and uses the test environment's env vars to connect to the
cluster.

  -provision.terraform=string   pass file generated by terraform output
  -provision.vagrant=string     provision to a single-node vagrant box

Nomad version flags tell the provisioner to deploy a specific version of
Nomad. These flags are all ignored if no '-provision.*' flag is set.
Otherwise at most one should be set.

  -nomad.local_file=string  provision this specific local binary of Nomad
  -nomad.sha=string         provision this specific sha from S3
  -nomad.version=string     provision this version from releases.hashicorp.com

TestSuites can request Constraints on the Framework.Environment so that tests
are only run in the appropriate conditions. These environment flags provide
the information for those constraints.

  -env=string           name of the environment
  -env.arch=string      cpu architecture of the targets
  -env.os=string        operating system of the targets
  -env.provider=string  cloud provider of the environment
  -env.tags=string      comma delimited list of tags for the environment

`

var fHelp = flag.Bool("showHelp", false, "print the help screen")
var fLocal = flag.Bool("local", false,
	"denotes execution is against a local environment, forcing default no-op provisioning")
var fSlow = flag.Bool("slow", false, "toggles execution of slow test suites")
var fForceRun = flag.Bool("forceRun", false,
	"if set, skips all environment checks when filtering test suites")
var fSkipTests = flag.Bool("skipTests", false, "skip all tests and only provision")
var fSuite = flag.String("suite", "", "run specified test suite")

// Provisioning flags
var fProvisionVagrant = flag.String("provision.vagrant", "",
	"run pre-provision to a single-node vagrant host")
var fProvisionTerraform = flag.String("provision.terraform", "",
	"run pre-provision from file generated by 'terraform output provisioning'")

// Nomad version flags
// TODO: these override each other. local_file > sha > version
// but we should assert at most 1 is set.
var fProvisionNomadLocalBinary = flag.String("nomad.local_file", "",
	"provision this specific local binary of Nomad (ignored for no-op provisioning).")
var fProvisionNomadSha = flag.String("nomad.sha", "",
	"provision this specific sha of Nomad (ignored for no-op provisioning)")
var fProvisionNomadVersion = flag.String("nomad.version", "",
	"provision this specific release of Nomad (ignored for no-op provisioning)")

// Environment flags
// TODO:
// if we have a provisioner, each target has its own environment. it'd
// be nice if we could match that environment against the tests so that
// we always avoid running tests that don't apply against the
// environment, and then have these flags override that behavior.
var fEnv = flag.String("env", "", "name of the environment executing against")
var fProvider = flag.String("env.provider", "",
	"cloud provider for which environment is executing against")
var fOS = flag.String("env.os", "",
	"operating system for which the environment is executing against")
var fArch = flag.String("env.arch", "",
	"cpu architecture for which the environment is executing against")
var fTags = flag.String("env.tags", "",
	"comma delimited list of tags associated with the environment")

var pkgFramework = New()

type Framework struct {
	suites      []*TestSuite
	provisioner provisioning.Provisioner
	env         Environment

	isLocalRun bool
	slow       bool
	force      bool
	skipAll    bool
	suite      string
}

// Environment contains information about the test target environment, used
// to constrain the set of tests run. See the environment flags above.
type Environment struct {
	Name     string
	Provider string
	OS       string
	Arch     string
	Tags     map[string]struct{}
}

// New creates a Framework
func New() *Framework {
	flag.Parse()
	if *fHelp {
		log.Fatal(frameworkHelp)
	}
	env := Environment{
		Name:     *fEnv,
		Provider: *fProvider,
		OS:       *fOS,
		Arch:     *fArch,
		Tags:     map[string]struct{}{},
	}
	for _, tag := range strings.Split(*fTags, ",") {
		env.Tags[tag] = struct{}{}
	}
	return &Framework{
		provisioner: provisioning.NewProvisioner(provisioning.ProvisionerConfig{
			IsLocal:          *fLocal,
			VagrantBox:       *fProvisionVagrant,
			TerraformConfig:  *fProvisionTerraform,
			NomadLocalBinary: *fProvisionNomadLocalBinary,
			NomadSha:         *fProvisionNomadSha,
			NomadVersion:     *fProvisionNomadVersion,
		}),
		env:        env,
		isLocalRun: *fLocal,
		slow:       *fSlow,
		force:      *fForceRun,
		skipAll:    *fSkipTests,
		suite:      *fSuite,
	}
}

// AddSuites adds a set of test suites to a Framework
func (f *Framework) AddSuites(s ...*TestSuite) *Framework {
	f.suites = append(f.suites, s...)
	return f
}

// AddSuites adds a set of test suites to the package scoped Framework
func AddSuites(s ...*TestSuite) *Framework {
	pkgFramework.AddSuites(s...)
	return pkgFramework
}

// Run starts the test framework, running each TestSuite
func (f *Framework) Run(t *testing.T) {
	info, err := f.provisioner.SetupTestRun(t, provisioning.SetupOptions{})
	if err != nil {
		t.Fatalf("could not provision cluster: %v", err)
	}
	defer f.provisioner.TearDownTestRun(t, info.ID)

	if f.skipAll {
		t.Skip("Skipping all tests, -skipTests set")
	}

	for _, s := range f.suites {
		t.Run(s.Component, func(t *testing.T) {
			skip, err := f.runSuite(t, s)
			if skip {
				t.Skipf("skipping suite '%s': %v", s.Component, err)
				return
			}
			if err != nil {
				t.Errorf("error starting suite '%s': %v", s.Component, err)
			}
		})
	}
}

// Run starts the package scoped Framework, running each TestSuite
func Run(t *testing.T) {
	pkgFramework.Run(t)
}

// runSuite is called from Framework.Run inside of a sub test for each TestSuite.
// If skip is returned as true, the test suite is skipped with the error text added
// to the Skip reason
// If skip is false and an error is returned, the test suite is failed.
func (f *Framework) runSuite(t *testing.T, s *TestSuite) (skip bool, err error) {

	// If -forceRun is set, skip all constraint checks
	if !f.force {
		// If this is a local run, check that the suite supports running locally
		if !s.CanRunLocal && f.isLocalRun {
			return true, fmt.Errorf("local run detected and suite cannot run locally")
		}

		// Check that constraints are met
		if err := s.Constraints.matches(f.env); err != nil {
			return true, fmt.Errorf("constraint failed: %v", err)
		}

		// Check the slow toggle and if the suite's slow flag is that same
		if f.slow != s.Slow {
			return true, fmt.Errorf("framework slow suite configuration is %v but suite is %v", f.slow, s.Slow)
		}
	}

	// If -suite is set, skip any suite that is not the one specified.
	if f.suite != "" && f.suite != s.Component {
		return true, fmt.Errorf("only running suite %q", f.suite)
	}

	info, err := f.provisioner.SetupTestSuite(t, provisioning.SetupOptions{
		Name:         s.Component,
		ExpectConsul: s.Consul,
		ExpectVault:  s.Vault,
	})
	if err != nil {
		t.Fatalf("could not provision cluster: %v", err)
	}
	defer f.provisioner.TearDownTestSuite(t, info.ID)

	for _, c := range s.Cases {
		f.runCase(t, s, c)
	}

	return false, nil
}

func (f *Framework) runCase(t *testing.T, s *TestSuite, c TestCase) {

	// The test name is set to the name of the implementing type, including package
	name := fmt.Sprintf("%T", c)

	// The ClusterInfo handle should be used by each TestCase to isolate
	// job/task state created during the test.
	info, err := f.provisioner.SetupTestCase(t, provisioning.SetupOptions{
		Name:         name,
		ExpectConsul: s.Consul,
		ExpectVault:  s.Vault,
	})
	if err != nil {
		t.Errorf("could not provision cluster for case: %v", err)
	}
	defer f.provisioner.TearDownTestCase(t, info.ID)
	c.setClusterInfo(info)

	// Each TestCase runs as a subtest of the TestSuite
	t.Run(c.Name(), func(t *testing.T) {
		// If the TestSuite has Parallel set, all cases run in parallel
		if s.Parallel {
			t.Parallel()
		}

		f := newF(t)

		// Check if the case includes a before all function
		if beforeAllTests, ok := c.(BeforeAllTests); ok {
			beforeAllTests.BeforeAll(f)
		}

		// Check if the case includes an after all function at the end
		defer func() {
			if afterAllTests, ok := c.(AfterAllTests); ok {
				afterAllTests.AfterAll(f)
			}
		}()

		// Here we need to iterate through the methods of the case to find
		// ones that are test functions
		reflectC := reflect.TypeOf(c)
		for i := 0; i < reflectC.NumMethod(); i++ {
			method := reflectC.Method(i)
			if ok := isTestMethod(method.Name); !ok {
				continue
			}
			// Each test is run as its own sub test of the case
			// Test cases are never parallel
			t.Run(method.Name, func(t *testing.T) {

				cF := newFFromParent(f, t)
				if BeforeEachTest, ok := c.(BeforeEachTest); ok {
					BeforeEachTest.BeforeEach(cF)
				}
				defer func() {
					if afterEachTest, ok := c.(AfterEachTest); ok {
						afterEachTest.AfterEach(cF)
					}
				}()

				//Call the method
				method.Func.Call([]reflect.Value{reflect.ValueOf(c), reflect.ValueOf(cF)})
			})
		}
	})
}

func isTestMethod(m string) bool {
	if !strings.HasPrefix(m, "Test") {
		return false
	}

	// THINKING: adding flag to target a specific test or test regex?
	return true
}
<commit_msg>e2e: avoid parsing Args in pkg init<commit_after>package framework

import (
	"flag"
	"fmt"
	"log"
	"reflect"
	"strings"
	"testing"

	"github.com/hashicorp/nomad/e2e/framework/provisioning"
)

const frameworkHelp = `
Usage: go test -v ./e2e [options]

These flags are coarse overrides for the test environment.

  -forceRun    skip all environment checks when filtering test suites
  -local       force default no-op provisioning
  -skipTests   skips all tests and only provisions
  -slow        include execution of slow test suites
  -suite       run specified test suite
  -showHelp    shows this help text

Provisioning flags tell the test runner to pre-provision the cluster before
running all tests. These flags can be passed to 'go test'. If no
'-provision.*' flag is set, the test runner assumes the cluster has already
been configured and uses the test environment's env vars to connect to the
cluster.

  -provision.terraform=string   pass file generated by terraform output
  -provision.vagrant=string     provision to a single-node vagrant box

Nomad version flags tell the provisioner to deploy a specific version of
Nomad. These flags are all ignored if no '-provision.*' flag is set.
Otherwise at most one should be set.

  -nomad.local_file=string  provision this specific local binary of Nomad
  -nomad.sha=string         provision this specific sha from S3
  -nomad.version=string     provision this version from releases.hashicorp.com

TestSuites can request Constraints on the Framework.Environment so that tests
are only run in the appropriate conditions. These environment flags provide
the information for those constraints.

  -env=string           name of the environment
  -env.arch=string      cpu architecture of the targets
  -env.os=string        operating system of the targets
  -env.provider=string  cloud provider of the environment
  -env.tags=string      comma delimited list of tags for the environment

`

var fHelp = flag.Bool("showHelp", false, "print the help screen")
var fLocal = flag.Bool("local", false,
	"denotes execution is against a local environment, forcing default no-op provisioning")
var fSlow = flag.Bool("slow", false, "toggles execution of slow test suites")
var fForceRun = flag.Bool("forceRun", false,
	"if set, skips all environment checks when filtering test suites")
var fSkipTests = flag.Bool("skipTests", false, "skip all tests and only provision")
var fSuite = flag.String("suite", "", "run specified test suite")

// Provisioning flags
var fProvisionVagrant = flag.String("provision.vagrant", "",
	"run pre-provision to a single-node vagrant host")
var fProvisionTerraform = flag.String("provision.terraform", "",
	"run pre-provision from file generated by 'terraform output provisioning'")

// Nomad version flags
// TODO: these override each other. local_file > sha > version
// but we should assert at most 1 is set.
var fProvisionNomadLocalBinary = flag.String("nomad.local_file", "",
	"provision this specific local binary of Nomad (ignored for no-op provisioning).")
var fProvisionNomadSha = flag.String("nomad.sha", "",
	"provision this specific sha of Nomad (ignored for no-op provisioning)")
var fProvisionNomadVersion = flag.String("nomad.version", "",
	"provision this specific release of Nomad (ignored for no-op provisioning)")

// Environment flags
// TODO:
// if we have a provisioner, each target has its own environment. it'd
// be nice if we could match that environment against the tests so that
// we always avoid running tests that don't apply against the
// environment, and then have these flags override that behavior.
var fEnv = flag.String("env", "", "name of the environment executing against")
var fProvider = flag.String("env.provider", "",
	"cloud provider for which environment is executing against")
var fOS = flag.String("env.os", "",
	"operating system for which the environment is executing against")
var fArch = flag.String("env.arch", "",
	"cpu architecture for which the environment is executing against")
var fTags = flag.String("env.tags", "",
	"comma delimited list of tags associated with the environment")

type Framework struct {
	suites      []*TestSuite
	provisioner provisioning.Provisioner
	env         Environment

	isLocalRun bool
	slow       bool
	force      bool
	skipAll    bool
	suite      string
}

// Environment contains information about the test target environment, used
// to constrain the set of tests run. See the environment flags above.
type Environment struct {
	Name     string
	Provider string
	OS       string
	Arch     string
	Tags     map[string]struct{}
}

// New creates a Framework
func New() *Framework {
	flag.Parse()
	if *fHelp {
		log.Fatal(frameworkHelp)
	}
	env := Environment{
		Name:     *fEnv,
		Provider: *fProvider,
		OS:       *fOS,
		Arch:     *fArch,
		Tags:     map[string]struct{}{},
	}
	for _, tag := range strings.Split(*fTags, ",") {
		env.Tags[tag] = struct{}{}
	}
	return &Framework{
		provisioner: provisioning.NewProvisioner(provisioning.ProvisionerConfig{
			IsLocal:          *fLocal,
			VagrantBox:       *fProvisionVagrant,
			TerraformConfig:  *fProvisionTerraform,
			NomadLocalBinary: *fProvisionNomadLocalBinary,
			NomadSha:         *fProvisionNomadSha,
			NomadVersion:     *fProvisionNomadVersion,
		}),
		env:        env,
		isLocalRun: *fLocal,
		slow:       *fSlow,
		force:      *fForceRun,
		skipAll:    *fSkipTests,
		suite:      *fSuite,
	}
}

// AddSuites adds a set of test suites to a Framework
func (f *Framework) AddSuites(s ...*TestSuite) *Framework {
	f.suites = append(f.suites, s...)
	return f
}

var pkgSuites []*TestSuite

// AddSuites adds a set of test suites to the package scoped Framework
func AddSuites(s ...*TestSuite) {
	pkgSuites = append(pkgSuites, s...)
}

// Run starts the test framework, running each TestSuite
func (f *Framework) Run(t *testing.T) {
	info, err := f.provisioner.SetupTestRun(t, provisioning.SetupOptions{})
	if err != nil {
		t.Fatalf("could not provision cluster: %v", err)
	}
	defer f.provisioner.TearDownTestRun(t, info.ID)

	if f.skipAll {
		t.Skip("Skipping all tests, -skipTests set")
	}

	for _, s := range f.suites {
		t.Run(s.Component, func(t *testing.T) {
			skip, err := f.runSuite(t, s)
			if skip {
				t.Skipf("skipping suite '%s': %v", s.Component, err)
				return
			}
			if err != nil {
				t.Errorf("error starting suite '%s': %v", s.Component, err)
			}
		})
	}
}

// Run starts the package scoped Framework, running each TestSuite
func Run(t *testing.T) {
	f := New()
	f.AddSuites(pkgSuites...)
	f.Run(t)
}

// runSuite is called from Framework.Run inside of a sub test for each TestSuite.
// If skip is returned as true, the test suite is skipped with the error text added
// to the Skip reason
// If skip is false and an error is returned, the test suite is failed.
func (f *Framework) runSuite(t *testing.T, s *TestSuite) (skip bool, err error) {

	// If -forceRun is set, skip all constraint checks
	if !f.force {
		// If this is a local run, check that the suite supports running locally
		if !s.CanRunLocal && f.isLocalRun {
			return true, fmt.Errorf("local run detected and suite cannot run locally")
		}

		// Check that constraints are met
		if err := s.Constraints.matches(f.env); err != nil {
			return true, fmt.Errorf("constraint failed: %v", err)
		}

		// Check the slow toggle and if the suite's slow flag is that same
		if f.slow != s.Slow {
			return true, fmt.Errorf("framework slow suite configuration is %v but suite is %v", f.slow, s.Slow)
		}
	}

	// If -suite is set, skip any suite that is not the one specified.
	if f.suite != "" && f.suite != s.Component {
		return true, fmt.Errorf("only running suite %q", f.suite)
	}

	info, err := f.provisioner.SetupTestSuite(t, provisioning.SetupOptions{
		Name:         s.Component,
		ExpectConsul: s.Consul,
		ExpectVault:  s.Vault,
	})
	if err != nil {
		t.Fatalf("could not provision cluster: %v", err)
	}
	defer f.provisioner.TearDownTestSuite(t, info.ID)

	for _, c := range s.Cases {
		f.runCase(t, s, c)
	}

	return false, nil
}

func (f *Framework) runCase(t *testing.T, s *TestSuite, c TestCase) {

	// The test name is set to the name of the implementing type, including package
	name := fmt.Sprintf("%T", c)

	// The ClusterInfo handle should be used by each TestCase to isolate
	// job/task state created during the test.
	info, err := f.provisioner.SetupTestCase(t, provisioning.SetupOptions{
		Name:         name,
		ExpectConsul: s.Consul,
		ExpectVault:  s.Vault,
	})
	if err != nil {
		t.Errorf("could not provision cluster for case: %v", err)
	}
	defer f.provisioner.TearDownTestCase(t, info.ID)
	c.setClusterInfo(info)

	// Each TestCase runs as a subtest of the TestSuite
	t.Run(c.Name(), func(t *testing.T) {
		// If the TestSuite has Parallel set, all cases run in parallel
		if s.Parallel {
			t.Parallel()
		}

		f := newF(t)

		// Check if the case includes a before all function
		if beforeAllTests, ok := c.(BeforeAllTests); ok {
			beforeAllTests.BeforeAll(f)
		}

		// Check if the case includes an after all function at the end
		defer func() {
			if afterAllTests, ok := c.(AfterAllTests); ok {
				afterAllTests.AfterAll(f)
			}
		}()

		// Here we need to iterate through the methods of the case to find
		// ones that are test functions
		reflectC := reflect.TypeOf(c)
		for i := 0; i < reflectC.NumMethod(); i++ {
			method := reflectC.Method(i)
			if ok := isTestMethod(method.Name); !ok {
				continue
			}
			// Each test is run as its own sub test of the case
			// Test cases are never parallel
			t.Run(method.Name, func(t *testing.T) {

				cF := newFFromParent(f, t)
				if BeforeEachTest, ok := c.(BeforeEachTest); ok {
					BeforeEachTest.BeforeEach(cF)
				}
				defer func() {
					if afterEachTest, ok := c.(AfterEachTest); ok {
						afterEachTest.AfterEach(cF)
					}
				}()

				//Call the method
				method.Func.Call([]reflect.Value{reflect.ValueOf(c), reflect.ValueOf(cF)})
			})
		}
	})
}

func isTestMethod(m string) bool {
	if !strings.HasPrefix(m, "Test") {
		return false
	}

	// THINKING: adding flag to target a specific test or test regex?
	return true
}
<|endoftext|>


<commit_before>package config

import "github.com/flant/dapp/pkg/config/ruby_marshal_config"

type GitManager struct {
	Local  []*GitLocal
	Remote []*GitRemote
}

func (c *GitManager) ToRuby() ruby_marshal_config.GitArtifact {
	rubyGitArtifactLocal := ruby_marshal_config.GitArtifactLocal{}
	for _, local := range c.Local {
		rubyGitArtifactLocal.Export = append(rubyGitArtifactLocal.Export, local.ToRuby())
	}

	rubyGitArtifactRemote := ruby_marshal_config.GitArtifactRemote{}
	for _, remote := range c.Remote {
		rubyGitArtifactRemote.Export = append(rubyGitArtifactRemote.Export, remote.ToRuby())
	}

	return ruby_marshal_config.GitArtifact{
		Local:  []ruby_marshal_config.GitArtifactLocal{rubyGitArtifactLocal},
		Remote: []ruby_marshal_config.GitArtifactRemote{rubyGitArtifactRemote},
	}
}
<commit_msg>Config: GitManager.ToRuby()<commit_after>package config

import "github.com/flant/dapp/pkg/config/ruby_marshal_config"

type GitManager struct {
	Local  []*GitLocal
	Remote []*GitRemote
}

func (c *GitManager) ToRuby() ruby_marshal_config.GitArtifact {
	gitArtifact := &ruby_marshal_config.GitArtifact{}

	if len(c.Local) != 0 {
		rubyGitArtifactLocal := ruby_marshal_config.GitArtifactLocal{}
		for _, local := range c.Local {
			rubyGitArtifactLocal.Export = append(rubyGitArtifactLocal.Export, local.ToRuby())
		}
		gitArtifact.Local = []ruby_marshal_config.GitArtifactLocal{rubyGitArtifactLocal}
	}

	if len(c.Remote) != 0 {
		rubyGitArtifactRemote := ruby_marshal_config.GitArtifactRemote{}
		for _, remote := range c.Remote {
			rubyGitArtifactRemote.Export = append(rubyGitArtifactRemote.Export, remote.ToRuby())
		}
		gitArtifact.Remote = []ruby_marshal_config.GitArtifactRemote{rubyGitArtifactRemote}
	}

	return *gitArtifact
}
<|endoftext|>