<commit_before>package main

import (
	"bufio"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
	"time"

	"code.google.com/p/go-uuid/uuid"

	"github.com/docker-exec/dexec/cli"
	"github.com/docker-exec/dexec/util"
	"github.com/fsouza/go-dockerclient"
)

// DexecImage consists of the file extension, Docker image name and Docker
// image version to use for a given Docker Exec image.
type DexecImage struct {
	name      string
	extension string
	image     string
	version   string
}

const dexecPath = "/tmp/dexec/build"
const dexecImageTemplate = "%s:%s"
const dexecVolumeTemplate = "%s/%s:%s/%s"
const dexecSanitisedWindowsPathPattern = "/%s%s"

var innerMap = map[string]*DexecImage{
	"c":      {"C", "c", "dexec/lang-c", "1.0.2"},
	"clj":    {"Clojure", "clj", "dexec/lang-clojure", "1.0.1"},
	"coffee": {"CoffeeScript", "coffee", "dexec/lang-coffee", "1.0.2"},
	"cpp":    {"C++", "cpp", "dexec/lang-cpp", "1.0.2"},
	"cs":     {"C#", "cs", "dexec/lang-csharp", "1.0.2"},
	"d":      {"D", "d", "dexec/lang-d", "1.0.1"},
	"erl":    {"Erlang", "erl", "dexec/lang-erlang", "1.0.1"},
	"fs":     {"F#", "fs", "dexec/lang-fsharp", "1.0.2"},
	"go":     {"Go", "go", "dexec/lang-go", "1.0.1"},
	"groovy": {"Groovy", "groovy", "dexec/lang-groovy", "1.0.1"},
	"hs":     {"Haskell", "hs", "dexec/lang-haskell", "1.0.1"},
	"java":   {"Java", "java", "dexec/lang-java", "1.0.2"},
	"lisp":   {"Lisp", "lisp", "dexec/lang-lisp", "1.0.1"},
	"lua":    {"Lua", "lua", "dexec/lang-lua", "1.0.1"},
	"js":     {"JavaScript", "js", "dexec/lang-node", "1.0.2"},
	"nim":    {"Nim", "nim", "dexec/lang-nim", "1.0.1"},
	"m":      {"Objective C", "m", "dexec/lang-objc", "1.0.1"},
	"ml":     {"OCaml", "ml", "dexec/lang-ocaml", "1.0.1"},
	"p6":     {"Perl 6", "p6", "dexec/lang-perl6", "1.0.1"},
	"pl":     {"Perl", "pl", "dexec/lang-perl", "1.0.2"},
	"php":    {"PHP", "php", "dexec/lang-php", "1.0.1"},
	"py":     {"Python", "py", "dexec/lang-python", "1.0.2"},
	"r":      {"R", "r", "dexec/lang-r", "1.0.1"},
	"rkt":    {"Racket", "rkt", "dexec/lang-racket", "1.0.1"},
	"rb":     {"Ruby", "rb", "dexec/lang-ruby", "1.0.1"},
	"rs":     {"Rust", "rs", "dexec/lang-rust", "1.0.1"},
	"scala":  {"Scala", "scala", "dexec/lang-scala", "1.0.1"},
	"sh":     {"Bash", "sh", "dexec/lang-bash", "1.0.1"},
}

// LookupImageByExtension returns the image for a given extension.
func LookupImageByExtension(key string) (*DexecImage, error) {
	if val, ok := innerMap[key]; ok {
		return val, nil
	}
	return nil, fmt.Errorf("Map does not contain key")
}

// LookupImageByName returns the image for a given image name.
func LookupImageByName(name string) (*DexecImage, error) {
	for _, v := range innerMap {
		if v.image == name {
			return v, nil
		}
	}
	return nil, fmt.Errorf("Map does not contain key")
}

// LookupImageByOverride takes an image that has been specified by the user
// to use instead of the one in the extension map. This function returns a
// DexecImage struct containing the image name & version, as well as the
// file extension that was passed in.
func LookupImageByOverride(image string, extension string) (*DexecImage, error) {
	patternImage := regexp.MustCompile(`(.*):(.*)`)
	imageMatch := patternImage.FindStringSubmatch(image)
	if len(imageMatch) > 0 {
		return &DexecImage{
			"Unknown",
			extension,
			imageMatch[1],
			imageMatch[2],
		}, nil
	}
	return &DexecImage{
		"Unknown",
		extension,
		image,
		"latest",
	}, nil
}

func imageFromOptions(options map[cli.OptionType][]string) *DexecImage {
	var image *DexecImage

	if useStdin := len(options[cli.Source]) == 0; useStdin {
		extensionOverride := len(options[cli.Extension]) == 1
		if extensionOverride {
			image, _ = LookupImageByExtension(options[cli.Extension][0])
		} else {
			overrideImage, _ := LookupImageByOverride(options[cli.SpecifyImage][0], "unknown")
			image, _ = LookupImageByName(overrideImage.image)
			image.version = overrideImage.version
		}
	} else {
		extension := util.ExtractFileExtension(options[cli.Source][0])
		image, _ = LookupImageByExtension(extension)
		imageOverride := len(options[cli.SpecifyImage]) == 1
		extensionOverride := len(options[cli.Extension]) == 1
		if extensionOverride {
			image, _ = LookupImageByExtension(options[cli.Extension][0])
		} else if imageOverride {
			image, _ = LookupImageByOverride(options[cli.SpecifyImage][0], extension)
		}
	}
	return image
}

// ExtractBasenameAndPermission takes an include string and splits it into
// its file or folder name and the permission string if present or the empty
// string if not.
func ExtractBasenameAndPermission(path string) (string, string) {
	pathPattern := regexp.MustCompile("([\\w.:-]+)(:(rw|ro))")
	match := pathPattern.FindStringSubmatch(path)

	basename := path
	var permission string

	if len(match) == 4 {
		basename = match[1]
		permission = match[2]
	}
	return basename, permission
}

// BuildVolumeArgs takes a base path and returns an array of Docker volume
// arguments. The array takes the form {"-v", "/foo:/bar:[rw|ro]", ...} for
// each source or include.
func BuildVolumeArgs(path string, targets []string) []string {
	var volumeArgs []string

	for _, source := range targets {
		basename, _ := ExtractBasenameAndPermission(source)

		volumeArgs = append(
			volumeArgs,
			fmt.Sprintf(dexecVolumeTemplate, path, basename, dexecPath, source),
		)
	}
	return volumeArgs
}

// SanitisePath takes an absolute path as provided by filepath.Abs() and
// makes it ready to be passed to Docker based on the current OS. So far
// the only OS format that requires transforming is Windows which is provided
// in the form 'C:\some\path' but Docker requires '/c/some/path'.
func SanitisePath(path string, platform string) string {
	sanitised := path
	if platform == "windows" {
		windowsPathPattern := regexp.MustCompile("^([A-Za-z]):(.*)")
		match := windowsPathPattern.FindStringSubmatch(path)

		driveLetter := strings.ToLower(match[1])
		pathRemainder := strings.Replace(match[2], "\\", "/", -1)

		sanitised = fmt.Sprintf(dexecSanitisedWindowsPathPattern, driveLetter, pathRemainder)
	}
	return sanitised
}

// RetrievePath takes an array whose first element may contain an overridden
// path and converts either this, or the default of "." to an absolute path
// using Go's file utilities. This is then passed to SanitisedPath with the
// current OS to get it into a Docker ready format.
func RetrievePath(targetDirs []string) string {
	path := "."
	if len(targetDirs) > 0 {
		path = targetDirs[0]
	}
	absPath, _ := filepath.Abs(path)
	return SanitisePath(absPath, runtime.GOOS)
}

func readStdin() []string {
	stat, _ := os.Stdin.Stat()
	isPipe := (stat.Mode() & os.ModeCharDevice) == 0
	if !isPipe {
		fmt.Fprintln(os.Stderr, "Enter your code. Ctrl-D to exit")
	}
	lines := []string{}
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		lines = append(lines, scanner.Text())
	}
	if !isPipe {
		fmt.Fprintf(os.Stderr, "<Ctrl-D>\n")
	}
	if err := scanner.Err(); err != nil {
		log.Fatal(scanner.Err())
	}
	return lines
}

// RunDexecContainer runs an anonymous Docker container with a Docker Exec
// image, mounting the specified sources and includes and passing the
// list of sources and arguments to the entrypoint.
func RunDexecContainer(options map[cli.OptionType][]string) {
	dexecImage := imageFromOptions(options)
	dockerImage := fmt.Sprintf(dexecImageTemplate, dexecImage.image, dexecImage.version)

	client, err := docker.NewClientFromEnv()
	image, err := client.InspectImage(dockerImage)

	if len(options[cli.UpdateFlag]) > 0 || image == nil {
		client.PullImage(docker.PullImageOptions{
			Repository: dexecImage.image,
			Tag:        dexecImage.version,
		}, docker.AuthConfiguration{})

		image, err = client.InspectImage(dockerImage)
		if err != nil {
			log.Fatal(err)
		} else if image == nil {
			log.Fatalf("Error retrieving image %s:%s", dexecImage.image, dexecImage.version)
		}
	}

	if err != nil {
		log.Fatal(err)
	}

	if useStdin := len(options[cli.Source]) == 0; useStdin {
		tmpFile := fmt.Sprintf("%s.%s", uuid.NewUUID().String(), dexecImage.extension)
		util.WriteFile(tmpFile, []byte(strings.Join(readStdin(), "\n")))

		defer func() {
			util.DeleteFile(tmpFile)
		}()

		options[cli.Source] = []string{tmpFile}
	}

	var sourceBasenames []string
	for _, source := range options[cli.Source] {
		basename, _ := ExtractBasenameAndPermission(source)
		sourceBasenames = append(sourceBasenames, []string{basename}...)
	}

	entrypointArgs := util.JoinStringSlices(
		sourceBasenames,
		util.AddPrefix(options[cli.BuildArg], "-b"),
		util.AddPrefix(options[cli.Arg], "-a"),
	)

	container, err := client.CreateContainer(docker.CreateContainerOptions{
		Config: &docker.Config{
			Image: dockerImage,
			Cmd:   entrypointArgs,
		},
	})

	if err != nil {
		log.Fatal(err)
	}

	err = client.StartContainer(container.ID, &docker.HostConfig{
		Binds: BuildVolumeArgs(
			RetrievePath(options[cli.TargetDir]),
			append(options[cli.Source], options[cli.Include]...)),
	})

	if err != nil {
		log.Fatal(err)
	}

	client.AttachToContainer(docker.AttachToContainerOptions{
		Container:    container.ID,
		OutputStream: os.Stdout,
		ErrorStream:  os.Stderr,
		Stream:       true,
		Stdout:       true,
		Stderr:       true,
		Logs:         true,
	})

	if err != nil {
		log.Fatal(err)
	}

	err = client.RemoveContainer(docker.RemoveContainerOptions{
		ID: container.ID,
	})
	if err != nil {
		log.Fatal(err)
	}
}

func validate(cliParser cli.CLI) bool {
	options := cliParser.Options

	hasVersionFlag := len(options[cli.VersionFlag]) == 1
	hasExtension := len(options[cli.Extension]) == 1
	hasImage := len(options[cli.SpecifyImage]) == 1
	hasSources := len(options[cli.Source]) > 0

	if hasSources || hasImage || hasExtension {
		return true
	}

	if hasVersionFlag {
		cli.DisplayVersion(cliParser.Filename)
		return false
	}

	cli.DisplayHelp(cliParser.Filename)
	return false
}

func validateDocker() error {
	client, err := docker.NewClientFromEnv()
	if err != nil {
		return err
	}

	timeout := time.After(5 * time.Second)

	ch := make(chan error, 1)
	go func() {
		ch <- client.Ping()
	}()

	select {
	case err := <-ch:
		return err
	case <-timeout:
		return fmt.Errorf("Request to Docker host timed out")
	}
}

func main() {
	cliParser := cli.ParseOsArgs(os.Args)

	if validate(cliParser) {
		if err := validateDocker(); err != nil {
			log.Fatal(err)
		} else {
			RunDexecContainer(cliParser.Options)
		}
	}
}
<commit_msg>factor out pulling image<commit_after>package main

import (
	"bufio"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
	"time"

	"code.google.com/p/go-uuid/uuid"

	"github.com/docker-exec/dexec/cli"
	"github.com/docker-exec/dexec/util"
	"github.com/fsouza/go-dockerclient"
)

// DexecImage consists of the file extension, Docker image name and Docker
// image version to use for a given Docker Exec image.
type DexecImage struct {
	name      string
	extension string
	image     string
	version   string
}

const dexecPath = "/tmp/dexec/build"
const dexecImageTemplate = "%s:%s"
const dexecVolumeTemplate = "%s/%s:%s/%s"
const dexecSanitisedWindowsPathPattern = "/%s%s"

var innerMap = map[string]*DexecImage{
	"c":      {"C", "c", "dexec/lang-c", "1.0.2"},
	"clj":    {"Clojure", "clj", "dexec/lang-clojure", "1.0.1"},
	"coffee": {"CoffeeScript", "coffee", "dexec/lang-coffee", "1.0.2"},
	"cpp":    {"C++", "cpp", "dexec/lang-cpp", "1.0.2"},
	"cs":     {"C#", "cs", "dexec/lang-csharp", "1.0.2"},
	"d":      {"D", "d", "dexec/lang-d", "1.0.1"},
	"erl":    {"Erlang", "erl", "dexec/lang-erlang", "1.0.1"},
	"fs":     {"F#", "fs", "dexec/lang-fsharp", "1.0.2"},
	"go":     {"Go", "go", "dexec/lang-go", "1.0.1"},
	"groovy": {"Groovy", "groovy", "dexec/lang-groovy", "1.0.1"},
	"hs":     {"Haskell", "hs", "dexec/lang-haskell", "1.0.1"},
	"java":   {"Java", "java", "dexec/lang-java", "1.0.2"},
	"lisp":   {"Lisp", "lisp", "dexec/lang-lisp", "1.0.1"},
	"lua":    {"Lua", "lua", "dexec/lang-lua", "1.0.1"},
	"js":     {"JavaScript", "js", "dexec/lang-node", "1.0.2"},
	"nim":    {"Nim", "nim", "dexec/lang-nim", "1.0.1"},
	"m":      {"Objective C", "m", "dexec/lang-objc", "1.0.1"},
	"ml":     {"OCaml", "ml", "dexec/lang-ocaml", "1.0.1"},
	"p6":     {"Perl 6", "p6", "dexec/lang-perl6", "1.0.1"},
	"pl":     {"Perl", "pl", "dexec/lang-perl", "1.0.2"},
	"php":    {"PHP", "php", "dexec/lang-php", "1.0.1"},
	"py":     {"Python", "py", "dexec/lang-python", "1.0.2"},
	"r":      {"R", "r", "dexec/lang-r", "1.0.1"},
	"rkt":    {"Racket", "rkt", "dexec/lang-racket", "1.0.1"},
	"rb":     {"Ruby", "rb", "dexec/lang-ruby", "1.0.1"},
	"rs":     {"Rust", "rs", "dexec/lang-rust", "1.0.1"},
	"scala":  {"Scala", "scala", "dexec/lang-scala", "1.0.1"},
	"sh":     {"Bash", "sh", "dexec/lang-bash", "1.0.1"},
}

// LookupImageByExtension returns the image for a given extension.
func LookupImageByExtension(key string) (*DexecImage, error) {
	if val, ok := innerMap[key]; ok {
		return val, nil
	}
	return nil, fmt.Errorf("Map does not contain key")
}

// LookupImageByName returns the image for a given image name.
func LookupImageByName(name string) (*DexecImage, error) {
	for _, v := range innerMap {
		if v.image == name {
			return v, nil
		}
	}
	return nil, fmt.Errorf("Map does not contain key")
}

// LookupImageByOverride takes an image that has been specified by the user
// to use instead of the one in the extension map. This function returns a
// DexecImage struct containing the image name & version, as well as the
// file extension that was passed in.
func LookupImageByOverride(image string, extension string) (*DexecImage, error) {
	patternImage := regexp.MustCompile(`(.*):(.*)`)
	imageMatch := patternImage.FindStringSubmatch(image)
	if len(imageMatch) > 0 {
		return &DexecImage{
			"Unknown",
			extension,
			imageMatch[1],
			imageMatch[2],
		}, nil
	}
	return &DexecImage{
		"Unknown",
		extension,
		image,
		"latest",
	}, nil
}

func imageFromOptions(options map[cli.OptionType][]string) *DexecImage {
	var image *DexecImage

	if useStdin := len(options[cli.Source]) == 0; useStdin {
		extensionOverride := len(options[cli.Extension]) == 1
		if extensionOverride {
			image, _ = LookupImageByExtension(options[cli.Extension][0])
		} else {
			overrideImage, _ := LookupImageByOverride(options[cli.SpecifyImage][0], "unknown")
			image, _ = LookupImageByName(overrideImage.image)
			image.version = overrideImage.version
		}
	} else {
		extension := util.ExtractFileExtension(options[cli.Source][0])
		image, _ = LookupImageByExtension(extension)
		imageOverride := len(options[cli.SpecifyImage]) == 1
		extensionOverride := len(options[cli.Extension]) == 1
		if extensionOverride {
			image, _ = LookupImageByExtension(options[cli.Extension][0])
		} else if imageOverride {
			image, _ = LookupImageByOverride(options[cli.SpecifyImage][0], extension)
		}
	}
	return image
}

// ExtractBasenameAndPermission takes an include string and splits it into
// its file or folder name and the permission string if present or the empty
// string if not.
func ExtractBasenameAndPermission(path string) (string, string) {
	pathPattern := regexp.MustCompile("([\\w.:-]+)(:(rw|ro))")
	match := pathPattern.FindStringSubmatch(path)

	basename := path
	var permission string

	if len(match) == 4 {
		basename = match[1]
		permission = match[2]
	}
	return basename, permission
}

// BuildVolumeArgs takes a base path and returns an array of Docker volume
// arguments. The array takes the form {"-v", "/foo:/bar:[rw|ro]", ...} for
// each source or include.
func BuildVolumeArgs(path string, targets []string) []string {
	var volumeArgs []string

	for _, source := range targets {
		basename, _ := ExtractBasenameAndPermission(source)

		volumeArgs = append(
			volumeArgs,
			fmt.Sprintf(dexecVolumeTemplate, path, basename, dexecPath, source),
		)
	}
	return volumeArgs
}

// SanitisePath takes an absolute path as provided by filepath.Abs() and
// makes it ready to be passed to Docker based on the current OS. So far
// the only OS format that requires transforming is Windows which is provided
// in the form 'C:\some\path' but Docker requires '/c/some/path'.
func SanitisePath(path string, platform string) string {
	sanitised := path
	if platform == "windows" {
		windowsPathPattern := regexp.MustCompile("^([A-Za-z]):(.*)")
		match := windowsPathPattern.FindStringSubmatch(path)

		driveLetter := strings.ToLower(match[1])
		pathRemainder := strings.Replace(match[2], "\\", "/", -1)

		sanitised = fmt.Sprintf(dexecSanitisedWindowsPathPattern, driveLetter, pathRemainder)
	}
	return sanitised
}

// RetrievePath takes an array whose first element may contain an overridden
// path and converts either this, or the default of "." to an absolute path
// using Go's file utilities. This is then passed to SanitisedPath with the
// current OS to get it into a Docker ready format.
func RetrievePath(targetDirs []string) string {
	path := "."
	if len(targetDirs) > 0 {
		path = targetDirs[0]
	}
	absPath, _ := filepath.Abs(path)
	return SanitisePath(absPath, runtime.GOOS)
}

func readStdin() []string {
	stat, _ := os.Stdin.Stat()
	isPipe := (stat.Mode() & os.ModeCharDevice) == 0
	if !isPipe {
		fmt.Fprintln(os.Stderr, "Enter your code. Ctrl-D to exit")
	}
	lines := []string{}
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		lines = append(lines, scanner.Text())
	}
	if !isPipe {
		fmt.Fprintf(os.Stderr, "<Ctrl-D>\n")
	}
	if err := scanner.Err(); err != nil {
		log.Fatal(scanner.Err())
	}
	return lines
}

func fetchImage(name string, tag string, update bool, client *docker.Client) error {
	dockerImage := fmt.Sprintf(dexecImageTemplate, name, tag)

	if _, err := client.InspectImage(dockerImage); update || err != nil {
		client.PullImage(docker.PullImageOptions{
			Repository: name,
			Tag:        tag,
		}, docker.AuthConfiguration{})

		if _, err = client.InspectImage(dockerImage); err != nil {
			return err
		}
	}
	return nil
}

// RunDexecContainer runs an anonymous Docker container with a Docker Exec
// image, mounting the specified sources and includes and passing the
// list of sources and arguments to the entrypoint.
func RunDexecContainer(options map[cli.OptionType][]string) {
	dexecImage := imageFromOptions(options)
	dockerImage := fmt.Sprintf(dexecImageTemplate, dexecImage.image, dexecImage.version)

	client, err := docker.NewClientFromEnv()

	if err != nil {
		log.Fatal(err)
	}

	if err := fetchImage(
		dexecImage.image,
		dexecImage.version,
		len(options[cli.UpdateFlag]) > 0,
		client); err != nil {
		log.Fatal(err)
	}

	if useStdin := len(options[cli.Source]) == 0; useStdin {
		tmpFile := fmt.Sprintf("%s.%s", uuid.NewUUID().String(), dexecImage.extension)
		util.WriteFile(tmpFile, []byte(strings.Join(readStdin(), "\n")))

		defer func() {
			util.DeleteFile(tmpFile)
		}()

		options[cli.Source] = []string{tmpFile}
	}

	var sourceBasenames []string
	for _, source := range options[cli.Source] {
		basename, _ := ExtractBasenameAndPermission(source)
		sourceBasenames = append(sourceBasenames, []string{basename}...)
	}

	entrypointArgs := util.JoinStringSlices(
		sourceBasenames,
		util.AddPrefix(options[cli.BuildArg], "-b"),
		util.AddPrefix(options[cli.Arg], "-a"),
	)

	container, err := client.CreateContainer(docker.CreateContainerOptions{
		Config: &docker.Config{
			Image: dockerImage,
			Cmd:   entrypointArgs,
		},
	})

	if err != nil {
		log.Fatal(err)
	}

	if err = client.StartContainer(container.ID, &docker.HostConfig{
		Binds: BuildVolumeArgs(
			RetrievePath(options[cli.TargetDir]),
			append(options[cli.Source], options[cli.Include]...)),
	}); err != nil {
		log.Fatal(err)
	}

	if err = client.AttachToContainer(docker.AttachToContainerOptions{
		Container:    container.ID,
		OutputStream: os.Stdout,
		ErrorStream:  os.Stderr,
		Stream:       true,
		Stdout:       true,
		Stderr:       true,
		Logs:         true,
	}); err != nil {
		log.Fatal(err)
	}

	if err = client.RemoveContainer(docker.RemoveContainerOptions{
		ID: container.ID,
	}); err != nil {
		log.Fatal(err)
	}
}

func validate(cliParser cli.CLI) bool {
	options := cliParser.Options

	hasVersionFlag := len(options[cli.VersionFlag]) == 1
	hasExtension := len(options[cli.Extension]) == 1
	hasImage := len(options[cli.SpecifyImage]) == 1
	hasSources := len(options[cli.Source]) > 0

	if hasSources || hasImage || hasExtension {
		return true
	}

	if hasVersionFlag {
		cli.DisplayVersion(cliParser.Filename)
		return false
	}

	cli.DisplayHelp(cliParser.Filename)
	return false
}

func validateDocker() error {
	client, err := docker.NewClientFromEnv()
	if err != nil {
		return err
	}

	ping := make(chan error, 1)
	go func() {
		ping <- client.Ping()
	}()

	select {
	case err := <-ping:
		return err
	case <-time.After(5 * time.Second):
		return fmt.Errorf("Request to Docker host timed out")
	}
}

func main() {
	cliParser := cli.ParseOsArgs(os.Args)

	if validate(cliParser) {
		if err := validateDocker(); err != nil {
			log.Fatal(err)
		} else {
			RunDexecContainer(cliParser.Options)
		}
	}
}
<|endoftext|>


<commit_before>package logmunch

import (
	"testing"
	"time"

	"encoding/json"
)

func TestLogLinesGetters(t *testing.T) {
	l := NewLogLine(time.Now(), "name", map[string]string{
		"i": "10",
		"f": "1.2",
		"s": "string",
		"m": "12ms",
	})

	// Existance and non-existance
	if !l.HasKey("i") {
		t.Errorf("HasKey(i) on %+v to be true\n", l)
	}
	if l.HasKey("missing") {
		t.Errorf("HasKey(missing) on %+v to be false\n", l)
	}

	// Integer as float
	n := l.GetNumber("i")
	if n != 10 {
		t.Errorf("Expected %+v to have i=10, got %+v\n", l, n)
	}

	// Float as float
	n = l.GetNumber("f")
	if n != 1.2 {
		t.Errorf("Expected %+v to have f=1.2, got %+v\n", l, n)
	}

	// String as float
	n = l.GetNumber("s")
	if n != 0 {
		t.Errorf("Expected %+v to have s=0, got %+v\n", l, n)
	}

	// Mixed as float
	n = l.GetNumber("m")
	if n != 12 {
		t.Errorf("Expected %+v to have m=12, got %+v\n", l, n)
	}

	// Missing as float
	n = l.GetNumber("missing")
	if n != 0 {
		t.Errorf("Expected %+v to have missing=0, got %+v\n", l, n)
	}
}

func TestLogLinesStringer(t *testing.T) {
	l := NewLogLine(
		time.Date(2015, 3, 29, 12, 29, 30, 5000000, time.UTC),
		"some prefix",
		map[string]string{
			"a": "first",
			"z": "last",
		})

	text := "2015-03-29T12:29:30.005Z some prefix a=first z=last"
	out := l.String()

	if out != text {
		t.Errorf("LogLine.String()\nreturned `%s`\nexpected `%s`", text, out)
	}
}

func TestLogLinesJSONEncoder(t *testing.T) {
	l := NewLogLine(
		time.Date(2015, 3, 29, 12, 29, 30, 5000000, time.UTC),
		"some prefix",
		map[string]string{
			"key": "first",
		},
	)

	// Encode to JSON
	j, err := json.Marshal(l)
	if err != nil {
		t.Fatalf("Failed mashalling JSON: %s", err)
	}

	t.Logf("%v", string(j))

	// Decode again
	var out map[string]interface{}
	err = json.Unmarshal(j, &out)
	if err != nil {
		t.Fatalf("Failed to unmarshal JSON: %s", err)
	}

	// Check date
	if date, ok := out["time"]; !ok {
		t.Errorf("JSON %s misses `time` field.", out)
	} else if date.(string) != l.Time.Format(time.RFC3339Nano) {
		t.Errorf("JSON %s .time returned `%s` field.", out, date)
	}

	// Check unix date
	if date, ok := out["unixtime"]; !ok {
		t.Errorf("JSON %s misses .unixtime.", out)
	} else if date.(float64) != 1427632170005 {
		t.Errorf("JSON %s .unixtime returned `%d`.", out, date)
	}

	// Check name
	if name, ok := out["name"]; !ok {
		t.Errorf("JSON %s misses `name` field.", out)
	} else if name.(string) != "some prefix" {
		t.Errorf("JSON %s .name returned `%s` field.", out, name)
	}

	// TODO Check key-values
	if _, ok := out["entries"]; !ok {
		t.Errorf("JSON %s misses .entries.", out)
	}
}
<commit_msg>LogLine test: Remove extraneous Logging.<commit_after>package logmunch

import (
	"testing"
	"time"

	"encoding/json"
)

func TestLogLinesGetters(t *testing.T) {
	l := NewLogLine(time.Now(), "name", map[string]string{
		"i": "10",
		"f": "1.2",
		"s": "string",
		"m": "12ms",
	})

	// Existance and non-existance
	if !l.HasKey("i") {
		t.Errorf("HasKey(i) on %+v to be true\n", l)
	}
	if l.HasKey("missing") {
		t.Errorf("HasKey(missing) on %+v to be false\n", l)
	}

	// Integer as float
	n := l.GetNumber("i")
	if n != 10 {
		t.Errorf("Expected %+v to have i=10, got %+v\n", l, n)
	}

	// Float as float
	n = l.GetNumber("f")
	if n != 1.2 {
		t.Errorf("Expected %+v to have f=1.2, got %+v\n", l, n)
	}

	// String as float
	n = l.GetNumber("s")
	if n != 0 {
		t.Errorf("Expected %+v to have s=0, got %+v\n", l, n)
	}

	// Mixed as float
	n = l.GetNumber("m")
	if n != 12 {
		t.Errorf("Expected %+v to have m=12, got %+v\n", l, n)
	}

	// Missing as float
	n = l.GetNumber("missing")
	if n != 0 {
		t.Errorf("Expected %+v to have missing=0, got %+v\n", l, n)
	}
}

func TestLogLinesStringer(t *testing.T) {
	l := NewLogLine(
		time.Date(2015, 3, 29, 12, 29, 30, 5000000, time.UTC),
		"some prefix",
		map[string]string{
			"a": "first",
			"z": "last",
		})

	text := "2015-03-29T12:29:30.005Z some prefix a=first z=last"
	out := l.String()

	if out != text {
		t.Errorf("LogLine.String()\nreturned `%s`\nexpected `%s`", text, out)
	}
}

func TestLogLinesJSONEncoder(t *testing.T) {
	l := NewLogLine(
		time.Date(2015, 3, 29, 12, 29, 30, 5000000, time.UTC),
		"some prefix",
		map[string]string{
			"key": "first",
		},
	)

	// Encode to JSON
	j, err := json.Marshal(l)
	if err != nil {
		t.Fatalf("Failed mashalling JSON: %s", err)
	}

	// Decode again
	var out map[string]interface{}
	err = json.Unmarshal(j, &out)
	if err != nil {
		t.Fatalf("Failed to unmarshal JSON: %s", err)
	}

	// Check date
	if date, ok := out["time"]; !ok {
		t.Errorf("JSON %s misses `time` field.", out)
	} else if date.(string) != l.Time.Format(time.RFC3339Nano) {
		t.Errorf("JSON %s .time returned `%s` field.", out, date)
	}

	// Check unix date
	if date, ok := out["unixtime"]; !ok {
		t.Errorf("JSON %s misses .unixtime.", out)
	} else if date.(float64) != 1427632170005 {
		t.Errorf("JSON %s .unixtime returned `%d`.", out, date)
	}

	// Check name
	if name, ok := out["name"]; !ok {
		t.Errorf("JSON %s misses `name` field.", out)
	} else if name.(string) != "some prefix" {
		t.Errorf("JSON %s .name returned `%s` field.", out, name)
	}

	// TODO Check key-values
	if _, ok := out["entries"]; !ok {
		t.Errorf("JSON %s misses .entries.", out)
	}
}
<|endoftext|>


<commit_before><commit_msg>Refactor service tests to use a FakeService rather than TermboxUI.<commit_after><|endoftext|>


<commit_before>package router

import (
	"fmt"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/micro/go-micro/registry"
	"github.com/olekukonko/tablewriter"
)

type router struct {
	opts Options
	exit chan struct{}
	wg   *sync.WaitGroup
}

// newRouter creates new router and returns it
func newRouter(opts ...Option) Router {
	// get default options
	options := DefaultOptions()

	// apply requested options
	for _, o := range opts {
		o(&options)
	}

	return &router{
		opts: options,
		exit: make(chan struct{}),
		wg:   &sync.WaitGroup{},
	}
}

// Init initializes router with given options
func (r *router) Init(opts ...Option) error {
	for _, o := range opts {
		o(&r.opts)
	}
	return nil
}

// Options returns router options
func (r *router) Options() Options {
	return r.opts
}

// ID returns router ID
func (r *router) ID() string {
	return r.opts.ID
}

// Table returns routing table
func (r *router) Table() Table {
	return r.opts.Table
}

// Address returns router's bind address
func (r *router) Address() string {
	return r.opts.Address
}

// Gossip returns gossip bind address
func (r *router) Gossip() string {
	return r.opts.GossipAddress
}

// Network returns router's micro network
func (r *router) Network() string {
	return r.opts.NetworkAddress
}

// Start starts the router
func (r *router) Start() error {
	// add local service routes into the routing table
	if err := r.addServiceRoutes(r.opts.LocalRegistry, "local", DefaultLocalMetric); err != nil {
		return fmt.Errorf("failed adding routes for local services: %v", err)
	}

	// add network service routes into the routing table
	if err := r.addServiceRoutes(r.opts.NetworkRegistry, r.opts.NetworkAddress, DefaultNetworkMetric); err != nil {
		return fmt.Errorf("failed adding routes for network services: %v", err)
	}

	// routing table has been bootstrapped;
	// NOTE: we only need to advertise local services upstream
	// lookup local service routes and advertise them upstream
	query := NewQuery(QueryNetwork("local"))
	localRoutes, err := r.opts.Table.Lookup(query)
	if err != nil && err != ErrRouteNotFound {
		return fmt.Errorf("failed to lookup local service routes: %v", err)
	}

	node, err := r.parseToNode()
	if err != nil {
		return fmt.Errorf("failed to parse router into service node: %v", err)
	}

	for _, route := range localRoutes {
		service := &registry.Service{
			Name:  route.Options().DestAddr,
			Nodes: []*registry.Node{node},
		}
		if err := r.opts.NetworkRegistry.Register(service, registry.RegisterTTL(120*time.Second)); err != nil {
			return fmt.Errorf("failed to register service %s in network registry: %v", service.Name, err)
		}
	}

	localWatcher, err := r.opts.LocalRegistry.Watch()
	if err != nil {
		return fmt.Errorf("failed to create local registry watcher: %v", err)
	}

	networkWatcher, err := r.opts.NetworkRegistry.Watch()
	if err != nil {
		return fmt.Errorf("failed to create network registry watcher: %v", err)
	}

	// we only watch local netwrork entries which we then propagate upstream to network
	tableWatcher, err := r.opts.Table.Watch(WatchNetwork("local"))
	if err != nil {
		return fmt.Errorf("failed to create routing table watcher: %v", err)
	}

	r.wg.Add(1)
	go r.manageServiceRoutes(localWatcher, "local", DefaultLocalMetric)

	r.wg.Add(1)
	go r.manageServiceRoutes(networkWatcher, r.opts.NetworkAddress, DefaultNetworkMetric)

	r.wg.Add(1)
	go r.watchTable(tableWatcher)

	return nil
}

// addServiceRouteslists all available services in given registry and adds them to the routing table.
// NOTE: this is a one-off operation done when bootstrapping the routing table of the new router.
// It returns error if either the services could not be listed or if the routes could not be added to the routing table.
func (r *router) addServiceRoutes(reg registry.Registry, network string, metric int) error {
	services, err := reg.ListServices()
	if err != nil {
		return fmt.Errorf("failed to list services: %v", err)
	}

	for _, service := range services {
		route := NewRoute(
			DestAddr(service.Name),
			Gateway(r),
			Network(network),
			Metric(metric),
		)
		if err := r.opts.Table.Add(route); err != nil && err != ErrDuplicateRoute {
			return fmt.Errorf("error adding route for service: %s", service.Name)
		}
	}

	return nil
}

// parseToNode parses router into registry.Node and returns the result.
// It returns error if the router network address could not be parsed into service host and port.
// NOTE: We use ":" as the default delimiter when we split the network address.
func (r *router) parseToNode() (*registry.Node, error) {
	// split on ":" as a standard host/port delimiter
	addr := strings.Split(r.opts.NetworkAddress, ":")
	// try to parse network port into integer
	port, err := strconv.Atoi(addr[1])
	if err != nil {
		return nil, fmt.Errorf("could not parse router network address from %s: %v", r.opts.NetworkAddress, err)
	}

	node := &registry.Node{
		Id:      r.opts.ID,
		Address: addr[0],
		Port:    port,
	}

	return node, nil
}

// manageServiceRoutes watches services in given registry and updates the routing table accordingly.
// It returns error if the service registry watcher has stopped or if the routing table failed to be updated.
func (r *router) manageServiceRoutes(w registry.Watcher, network string, metric int) error {
	defer r.wg.Done()

	// wait in the background for the router to stop
	// when the router stops, stop the watcher and exit
	r.wg.Add(1)
	go func() {
		defer r.wg.Done()
		<-r.exit
		w.Stop()
	}()

	var watchErr error

	// watch for changes to services
	for {
		res, err := w.Next()
		if err == registry.ErrWatcherStopped {
			break
		}

		if err != nil {
			watchErr = err
			break
		}

		route := NewRoute(
			DestAddr(res.Service.Name),
			Gateway(r),
			Network(network),
			Metric(metric),
		)

		switch res.Action {
		case "create":
			if len(res.Service.Nodes) > 0 {
				/// only return error if the route is not duplicate, but something else has failed
				if err := r.opts.Table.Add(route); err != nil && err != ErrDuplicateRoute {
					return fmt.Errorf("failed to add route for service: %v", res.Service.Name)
				}
			}
		case "delete":
			if len(res.Service.Nodes) <= 1 {
				// only return error if the route is present in the table, but something else has failed
				if err := r.opts.Table.Delete(route); err != nil && err != ErrRouteNotFound {
					return fmt.Errorf("failed to delete route for service: %v", res.Service.Name)
				}
			}
		}
	}

	return watchErr
}

// watchTable watches routing table entries and either adds or deletes locally registered service to/from network registry
// It returns error if the locally registered services either fails to be added/deleted to/from network registry.
func (r *router) watchTable(w Watcher) error {
	defer r.wg.Done()

	r.wg.Add(1)
	go func() {
		defer r.wg.Done()
		<-r.exit
		w.Stop()
	}()

	var watchErr error

	// watch for changes to services
	for {
		event, err := w.Next()
		if err == ErrWatcherStopped {
			break
		}

		if err != nil {
			watchErr = err
			break
		}

		node, err := r.parseToNode()
		if err != nil {
			return fmt.Errorf("failed to parse router into node: %v", err)
		}

		service := &registry.Service{
			Name:  event.Route.Options().DestAddr,
			Nodes: []*registry.Node{node},
		}

		switch event.Type {
		case CreateEvent:
			// only register remotely if the service is "local"
			if event.Route.Options().Network == "local" {
				if err := r.opts.NetworkRegistry.Register(service, registry.RegisterTTL(120*time.Second)); err != nil {
					return fmt.Errorf("failed to register service %s in network registry: %v", service.Name, err)
				}
			}
		case UpdateEvent:
			// only deregister remotely if the service is "local"
			if event.Route.Options().Network == "local" {
				if err := r.opts.NetworkRegistry.Deregister(service); err != nil {
					return fmt.Errorf("failed to deregister service %s from network registry: %v", service.Name, err)
				}
			}
		}
	}

	return watchErr
}

// Stop stops the router
func (r *router) Stop() error {
	// NOTE: we need a more efficient way of doing this e.g. network routes
	// should ideally be autodeleted when the router stops gossiping
	// deregister all services advertised by this router from remote registry
	query := NewQuery(QueryGateway(r), QueryNetwork(r.opts.NetworkAddress))
	routes, err := r.opts.Table.Lookup(query)
	if err != nil && err != ErrRouteNotFound {
		return fmt.Errorf("failed to lookup routes for router %s: %v", r.opts.ID, err)
	}

	node, err := r.parseToNode()
	if err != nil {
		return fmt.Errorf("failed to parse router into service node: %v", err)
	}

	for _, route := range routes {
		service := &registry.Service{
			Name:  route.Options().DestAddr,
			Nodes: []*registry.Node{node},
		}
		if err := r.opts.NetworkRegistry.Deregister(service); err != nil {
			return fmt.Errorf("failed to deregister service %s from network registry: %v", service.Name, err)
		}
	}

	// notify all goroutines to finish
	close(r.exit)

	// wait for all goroutines to finish
	r.wg.Wait()

	return nil
}

// String prints debugging information about router
func (r *router) String() string {
	sb := &strings.Builder{}

	table := tablewriter.NewWriter(sb)
	table.SetHeader([]string{"ID", "Address", "Network", "Table"})

	data := []string{
		r.opts.ID,
		r.opts.Address,
		r.opts.NetworkAddress,
		fmt.Sprintf("%d", r.opts.Table.Size()),
	}
	table.Append(data)

	// render table into sb
	table.Render()

	return sb.String()
}
<commit_msg>Delete route when no node is available.<commit_after>package router

import (
	"fmt"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/micro/go-micro/registry"
	"github.com/olekukonko/tablewriter"
)

type router struct {
	opts Options
	exit chan struct{}
	wg   *sync.WaitGroup
}

// newRouter creates new router and returns it
func newRouter(opts ...Option) Router {
	// get default options
	options := DefaultOptions()

	// apply requested options
	for _, o := range opts {
		o(&options)
	}

	return &router{
		opts: options,
		exit: make(chan struct{}),
		wg:   &sync.WaitGroup{},
	}
}

// Init initializes router with given options
func (r *router) Init(opts ...Option) error {
	for _, o := range opts {
		o(&r.opts)
	}
	return nil
}

// Options returns router options
func (r *router) Options() Options {
	return r.opts
}

// ID returns router ID
func (r *router) ID() string {
	return r.opts.ID
}

// Table returns routing table
func (r *router) Table() Table {
	return r.opts.Table
}

// Address returns router's bind address
func (r *router) Address() string {
	return r.opts.Address
}

// Gossip returns gossip bind address
func (r *router) Gossip() string {
	return r.opts.GossipAddress
}

// Network returns router's micro network
func (r *router) Network() string {
	return r.opts.NetworkAddress
}

// Start starts the router
func (r *router) Start() error {
	// add local service routes into the routing table
	if err := r.addServiceRoutes(r.opts.LocalRegistry, "local", DefaultLocalMetric); err != nil {
		return fmt.Errorf("failed adding routes for local services: %v", err)
	}

	// add network service routes into the routing table
	if err := r.addServiceRoutes(r.opts.NetworkRegistry, r.opts.NetworkAddress, DefaultNetworkMetric); err != nil {
		return fmt.Errorf("failed adding routes for network services: %v", err)
	}

	// routing table has been bootstrapped;
	// NOTE: we only need to advertise local services upstream
	// lookup local service routes and advertise them upstream
	query := NewQuery(QueryNetwork("local"))
	localRoutes, err := r.opts.Table.Lookup(query)
	if err != nil && err != ErrRouteNotFound {
		return fmt.Errorf("failed to lookup local service routes: %v", err)
	}

	node, err := r.parseToNode()
	if err != nil {
		return fmt.Errorf("failed to parse router into service node: %v", err)
	}

	for _, route := range localRoutes {
		service := &registry.Service{
			Name:  route.Options().DestAddr,
			Nodes: []*registry.Node{node},
		}
		if err := r.opts.NetworkRegistry.Register(service, registry.RegisterTTL(120*time.Second)); err != nil {
			return fmt.Errorf("failed to register service %s in network registry: %v", service.Name, err)
		}
	}

	localWatcher, err := r.opts.LocalRegistry.Watch()
	if err != nil {
		return fmt.Errorf("failed to create local registry watcher: %v", err)
	}

	networkWatcher, err := r.opts.NetworkRegistry.Watch()
	if err != nil {
		return fmt.Errorf("failed to create network registry watcher: %v", err)
	}

	// NOTE: we only watch local netwrork entries which we then propagate upstream to network
	tableWatcher, err := r.opts.Table.Watch(WatchNetwork("local"))
	if err != nil {
		return fmt.Errorf("failed to create routing table watcher: %v", err)
	}

	r.wg.Add(1)
	go r.manageServiceRoutes(localWatcher, "local", DefaultLocalMetric)

	r.wg.Add(1)
	go r.manageServiceRoutes(networkWatcher, r.opts.NetworkAddress, DefaultNetworkMetric)

	r.wg.Add(1)
	go r.watchTable(tableWatcher)

	return nil
}

// addServiceRouteslists all available services in given registry and adds them to the routing table.
// NOTE: this is a one-off operation done when bootstrapping the routing table of the new router.
// It returns error if either the services could not be listed or if the routes could not be added to the routing table.
func (r *router) addServiceRoutes(reg registry.Registry, network string, metric int) error {
	services, err := reg.ListServices()
	if err != nil {
		return fmt.Errorf("failed to list services: %v", err)
	}

	for _, service := range services {
		route := NewRoute(
			DestAddr(service.Name),
			Gateway(r),
			Network(network),
			Metric(metric),
		)
		if err := r.opts.Table.Add(route); err != nil && err != ErrDuplicateRoute {
			return fmt.Errorf("error adding route for service: %s", service.Name)
		}
	}

	return nil
}

// parseToNode parses router into registry.Node and returns the result.
// It returns error if the router network address could not be parsed into service host and port.
// NOTE: We use ":" as the default delimiter when we split the network address.
func (r *router) parseToNode() (*registry.Node, error) {
	// split on ":" as a standard host/port delimiter
	addr := strings.Split(r.opts.NetworkAddress, ":")
	// try to parse network port into integer
	port, err := strconv.Atoi(addr[1])
	if err != nil {
		return nil, fmt.Errorf("could not parse router network address from %s: %v", r.opts.NetworkAddress, err)
	}

	node := &registry.Node{
		Id:      r.opts.ID,
		Address: addr[0],
		Port:    port,
	}

	return node, nil
}

// manageServiceRoutes watches services in given registry and updates the routing table accordingly.
// It returns error if the service registry watcher has stopped or if the routing table failed to be updated.
func (r *router) manageServiceRoutes(w registry.Watcher, network string, metric int) error {
	defer r.wg.Done()

	// wait in the background for the router to stop
	// when the router stops, stop the watcher and exit
	r.wg.Add(1)
	go func() {
		defer r.wg.Done()
		<-r.exit
		w.Stop()
	}()

	var watchErr error

	// watch for changes to services
	for {
		res, err := w.Next()
		if err == registry.ErrWatcherStopped {
			break
		}

		if err != nil {
			watchErr = err
			break
		}

		route := NewRoute(
			DestAddr(res.Service.Name),
			Gateway(r),
			Network(network),
			Metric(metric),
		)

		switch res.Action {
		case "create":
			if len(res.Service.Nodes) > 0 {
				/// only return error if the route is not duplicate, but something else has failed
				if err := r.opts.Table.Add(route); err != nil && err != ErrDuplicateRoute {
					return fmt.Errorf("failed to add route for service: %v", res.Service.Name)
				}
			}
		case "delete":
			if len(res.Service.Nodes) < 1 {
				// only return error if the route is present in the table, but something else has failed
				if err := r.opts.Table.Delete(route); err != nil && err != ErrRouteNotFound {
					return fmt.Errorf("failed to delete route for service: %v", res.Service.Name)
				}
			}
		}
	}

	return watchErr
}

// watchTable watches routing table entries and either adds or deletes locally registered service to/from network registry
// It returns error if the locally registered services either fails to be added/deleted to/from network registry.
func (r *router) watchTable(w Watcher) error {
	defer r.wg.Done()

	r.wg.Add(1)
	go func() {
		defer r.wg.Done()
		<-r.exit
		w.Stop()
	}()

	var watchErr error

	// watch for changes to services
	for {
		event, err := w.Next()
		if err == ErrWatcherStopped {
			break
		}

		if err != nil {
			watchErr = err
			break
		}

		node, err := r.parseToNode()
		if err != nil {
			return fmt.Errorf("failed to parse router into node: %v", err)
		}

		// we know that .DestAddr contains the registered service name
		service := &registry.Service{
			Name:  event.Route.Options().DestAddr,
			Nodes: []*registry.Node{node},
		}

		switch event.Type {
		case CreateEvent:
			if err := r.opts.NetworkRegistry.Register(service, registry.RegisterTTL(120*time.Second)); err != nil {
				return fmt.Errorf("failed to register service %s in network registry: %v", service.Name, err)
			}
		case DeleteEvent:
			if err := r.opts.NetworkRegistry.Deregister(service); err != nil {
				return fmt.Errorf("failed to deregister service %s from network registry: %v", service.Name, err)
			}
		}
	}

	return watchErr
}

// Stop stops the router
func (r *router) Stop() error {
	// NOTE: we need a more efficient way of doing this e.g. network routes
	// should ideally be autodeleted when the router stops gossiping
	// deregister all services advertised by this router from remote registry
	query := NewQuery(QueryGateway(r), QueryNetwork(r.opts.NetworkAddress))
	routes, err := r.opts.Table.Lookup(query)
	if err != nil && err != ErrRouteNotFound {
		return fmt.Errorf("failed to lookup routes for router %s: %v", r.opts.ID, err)
	}

	node, err := r.parseToNode()
	if err != nil {
		return fmt.Errorf("failed to parse router into service node: %v", err)
	}

	for _, route := range routes {
		service := &registry.Service{
			Name:  route.Options().DestAddr,
			Nodes: []*registry.Node{node},
		}
		if err := r.opts.NetworkRegistry.Deregister(service); err != nil {
			return fmt.Errorf("failed to deregister service %s from network registry: %v", service.Name, err)
		}
	}

	// notify all goroutines to finish
	close(r.exit)

	// wait for all goroutines to finish
	r.wg.Wait()

	return nil
}

// String prints debugging information about router
func (r *router) String() string {
	sb := &strings.Builder{}

	table := tablewriter.NewWriter(sb)
	table.SetHeader([]string{"ID", "Address", "Network", "Table"})

	data := []string{
		r.opts.ID,
		r.opts.Address,
		r.opts.NetworkAddress,
		fmt.Sprintf("%d", r.opts.Table.Size()),
	}
	table.Append(data)

	// render table into sb
	table.Render()

	return sb.String()
}
<|endoftext|>


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

// Package hipache provides a router implementation that store routes in Redis,
// as specified by Hipache (https://github.com/dotcloud/hipache).
//
// It does not provided any exported type, in order to use the router, you must
// import this package and get the router intance using the function
// router.Get.
//
// In order to use this router, you need to define the "hipache:domain"
// setting.
package hipache

import (
	"errors"
	"fmt"
	"github.com/garyburd/redigo/redis"
	"github.com/globocom/config"
	"github.com/globocom/tsuru/router"
)

var conn redis.Conn

var errRouteNotFound = errors.New("Route not found")

func init() {
	router.Register("hipache", hipacheRouter{})
}

func connect() (redis.Conn, error) {
	if conn == nil {
		srv, err := config.GetString("hipache:redis-server")
		if err != nil {
			srv = "localhost:6379"
		}
		conn, err = redis.Dial("tcp", srv)
		if err != nil {
			return nil, err
		}
	}
	return conn, nil
}

type hipacheRouter struct{}

func (hipacheRouter) AddBackend(name string) error {
	domain, err := config.GetString("hipache:domain")
	if err != nil {
		return &routeError{"add", err}
	}
	frontend := "frontend:" + name + "." + domain
	conn, err := connect()
	if err != nil {
		return &routeError{"add", err}
	}
	defer conn.Close()
	_, err = conn.Do("RPUSH", frontend, name)
	if err != nil {
		return &routeError{"add", err}
	}
	return nil
}

func (r hipacheRouter) RemoveBackend(name string) error {
	domain, err := config.GetString("hipache:domain")
	if err != nil {
		return &routeError{"remove", err}
	}
	frontend := "frontend:" + name + "." + domain
	conn, err := connect()
	if err != nil {
		return &routeError{"remove", err}
	}
	defer conn.Close()
	_, err = conn.Do("DEL", frontend)
	if err != nil {
		return &routeError{"remove", err}
	}
	cname, err := r.getCName(name)
	if err != nil {
		return err
	}
	if cname == "" {
		return nil
	}
	_, err = conn.Do("DEL", "frontend:"+cname)
	if err != nil {
		return &routeError{"remove", err}
	}
	_, err = conn.Do("DEL", "cname:"+name)
	if err != nil {
		return &routeError{"remove", err}
	}
	return nil
}

func (r hipacheRouter) AddRoute(name, address string) error {
	domain, err := config.GetString("hipache:domain")
	if err != nil {
		return &routeError{"add", err}
	}
	frontend := "frontend:" + name + "." + domain
	if err := r.addRoute(frontend, address); err != nil {
		return &routeError{"add", err}
	}
	cname, err := r.getCName(name)
	if err != nil {
		return err
	}
	if cname == "" {
		return nil
	}
	return r.addRoute("frontend:"+cname, address)
}

func (hipacheRouter) addRoute(name, address string) error {
	conn, err := connect()
	if err != nil {
		return &routeError{"add", err}
	}
	defer conn.Close()
	_, err = conn.Do("RPUSH", name, address)
	if err != nil {
		return &routeError{"add", err}
	}
	return nil
}

func (r hipacheRouter) RemoveRoute(name, address string) error {
	domain, err := config.GetString("hipache:domain")
	if err != nil {
		return &routeError{"remove", err}
	}
	frontend := "frontend:" + name + "." + domain
	if err := r.removeElement(frontend, address); err != nil {
		return err
	}
	cname, err := r.getCName(name)
	if err != nil {
		return &routeError{"remove", err}
	}
	if cname == "" {
		return nil
	}
	return r.removeElement("frontend:"+cname, address)
}

func (hipacheRouter) getCName(name string) (string, error) {
	conn, err := connect()
	if err != nil {
		return "", &routeError{"getCName", err}
	}
	defer conn.Close()
	cname, err := redis.String(conn.Do("GET", "cname:"+name))
	if err != nil {
		return "", &routeError{"getCName", err}
	}
	return cname, nil
}

func (hipacheRouter) SetCName(cname, name string) error {
	domain, err := config.GetString("hipache:domain")
	if err != nil {
		return &routeError{"addCName", err}
	}
	frontend := "frontend:" + name + "." + domain
	conn, err := connect()
	if err != nil {
		return &routeError{"addCName", err}
	}
	defer conn.Close()
	addresses, err := redis.Strings(conn.Do("LRANGE", frontend, 0, -1))
	if err != nil {
		return &routeError{"get", err}
	}
	_, err = conn.Do("SET", "cname:"+name, cname)
	if err != nil {
		return &routeError{"set", err}
	}
	frontend = "frontend:" + cname
	for _, r := range addresses {
		_, err := conn.Do("RPUSH", frontend, r)
		if err != nil {
			return &routeError{"addCName", err}
		}
	}
	return nil
}

func (r hipacheRouter) UnsetCName(cname, name string) error {
	conn, err := connect()
	if err != nil {
		return &routeError{"removeCName", err}
	}
	defer conn.Close()
	_, err = conn.Do("DEL", "cname:"+name)
	if err != nil {
		return &routeError{"removeCName", err}
	}
	_, err = conn.Do("DEL", "frontend:"+cname)
	if err != nil {
		return &routeError{"removeCName", err}
	}
	return nil
}

func (hipacheRouter) Addr(name string) (string, error) {
	domain, err := config.GetString("hipache:domain")
	if err != nil {
		return "", &routeError{"get", err}
	}
	frontend := "frontend:" + name + "." + domain
	conn, err := connect()
	if err != nil {
		return "", &routeError{"get", err}
	}
	defer conn.Close()
	reply, err := conn.Do("LRANGE", frontend, 0, 0)
	if err != nil {
		return "", &routeError{"get", err}
	}
	backends := reply.([]interface{})
	if len(backends) < 1 {
		return "", errRouteNotFound
	}
	return fmt.Sprintf("%s.%s", name, domain), nil
}

func (hipacheRouter) removeElement(name, address string) error {
	conn, err := connect()
	if err != nil {
		return &routeError{"remove", err}
	}
	defer conn.Close()
	_, err = conn.Do("LREM", name, 0, address)
	if err != nil {
		return &routeError{"remove", err}
	}
	return nil
}

type routeError struct {
	op  string
	err error
}

func (e *routeError) Error() string {
	return fmt.Sprintf("Could not %s route: %s", e.op, e.err)
}
<commit_msg>Revert "router/hipache/router: deferring close of redis connections"<commit_after>// Copyright 2013 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package hipache provides a router implementation that store routes in Redis,
// as specified by Hipache (https://github.com/dotcloud/hipache).
//
// It does not provided any exported type, in order to use the router, you must
// import this package and get the router intance using the function
// router.Get.
//
// In order to use this router, you need to define the "hipache:domain"
// setting.
package hipache

import (
	"errors"
	"fmt"
	"github.com/garyburd/redigo/redis"
	"github.com/globocom/config"
	"github.com/globocom/tsuru/router"
)

var conn redis.Conn

var errRouteNotFound = errors.New("Route not found")

func init() {
	router.Register("hipache", hipacheRouter{})
}

func connect() (redis.Conn, error) {
	if conn == nil {
		srv, err := config.GetString("hipache:redis-server")
		if err != nil {
			srv = "localhost:6379"
		}
		conn, err = redis.Dial("tcp", srv)
		if err != nil {
			return nil, err
		}
	}
	return conn, nil
}

type hipacheRouter struct{}

func (hipacheRouter) AddBackend(name string) error {
	domain, err := config.GetString("hipache:domain")
	if err != nil {
		return &routeError{"add", err}
	}
	frontend := "frontend:" + name + "." + domain
	conn, err := connect()
	if err != nil {
		return &routeError{"add", err}
	}
	_, err = conn.Do("RPUSH", frontend, name)
	if err != nil {
		return &routeError{"add", err}
	}
	return nil
}

func (r hipacheRouter) RemoveBackend(name string) error {
	domain, err := config.GetString("hipache:domain")
	if err != nil {
		return &routeError{"remove", err}
	}
	frontend := "frontend:" + name + "." + domain
	conn, err := connect()
	if err != nil {
		return &routeError{"remove", err}
	}
	_, err = conn.Do("DEL", frontend)
	if err != nil {
		return &routeError{"remove", err}
	}
	cname, err := r.getCName(name)
	if err != nil {
		return err
	}
	if cname == "" {
		return nil
	}
	_, err = conn.Do("DEL", "frontend:"+cname)
	if err != nil {
		return &routeError{"remove", err}
	}
	_, err = conn.Do("DEL", "cname:"+name)
	if err != nil {
		return &routeError{"remove", err}
	}
	return nil
}

func (r hipacheRouter) AddRoute(name, address string) error {
	domain, err := config.GetString("hipache:domain")
	if err != nil {
		return &routeError{"add", err}
	}
	frontend := "frontend:" + name + "." + domain
	if err := r.addRoute(frontend, address); err != nil {
		return &routeError{"add", err}
	}
	cname, err := r.getCName(name)
	if err != nil {
		return err
	}
	if cname == "" {
		return nil
	}
	return r.addRoute("frontend:"+cname, address)
}

func (hipacheRouter) addRoute(name, address string) error {
	conn, err := connect()
	if err != nil {
		return &routeError{"add", err}
	}
	_, err = conn.Do("RPUSH", name, address)
	if err != nil {
		return &routeError{"add", err}
	}
	return nil
}

func (r hipacheRouter) RemoveRoute(name, address string) error {
	domain, err := config.GetString("hipache:domain")
	if err != nil {
		return &routeError{"remove", err}
	}
	frontend := "frontend:" + name + "." + domain
	if err := r.removeElement(frontend, address); err != nil {
		return err
	}
	cname, err := r.getCName(name)
	if err != nil {
		return &routeError{"remove", err}
	}
	if cname == "" {
		return nil
	}
	return r.removeElement("frontend:"+cname, address)
}

func (hipacheRouter) getCName(name string) (string, error) {
	conn, err := connect()
	if err != nil {
		return "", &routeError{"getCName", err}
	}
	defer conn.Close()
	cname, err := redis.String(conn.Do("GET", "cname:"+name))
	if err != nil {
		return "", &routeError{"getCName", err}
	}
	return cname, nil
}

func (hipacheRouter) SetCName(cname, name string) error {
	domain, err := config.GetString("hipache:domain")
	if err != nil {
		return &routeError{"addCName", err}
	}
	frontend := "frontend:" + name + "." + domain
	conn, err := connect()
	if err != nil {
		return &routeError{"addCName", err}
	}
	addresses, err := redis.Strings(conn.Do("LRANGE", frontend, 0, -1))
	if err != nil {
		return &routeError{"get", err}
	}
	_, err = conn.Do("SET", "cname:"+name, cname)
	if err != nil {
		return &routeError{"set", err}
	}
	frontend = "frontend:" + cname
	for _, r := range addresses {
		_, err := conn.Do("RPUSH", frontend, r)
		if err != nil {
			return &routeError{"addCName", err}
		}
	}
	return nil
}

func (r hipacheRouter) UnsetCName(cname, name string) error {
	conn, err := connect()
	if err != nil {
		return &routeError{"removeCName", err}
	}
	_, err = conn.Do("DEL", "cname:"+name)
	if err != nil {
		return &routeError{"removeCName", err}
	}
	_, err = conn.Do("DEL", "frontend:"+cname)
	if err != nil {
		return &routeError{"removeCName", err}
	}
	return nil
}

func (hipacheRouter) Addr(name string) (string, error) {
	domain, err := config.GetString("hipache:domain")
	if err != nil {
		return "", &routeError{"get", err}
	}
	frontend := "frontend:" + name + "." + domain
	conn, err := connect()
	if err != nil {
		return "", &routeError{"get", err}
	}
	reply, err := conn.Do("LRANGE", frontend, 0, 0)
	if err != nil {
		return "", &routeError{"get", err}
	}
	backends := reply.([]interface{})
	if len(backends) < 1 {
		return "", errRouteNotFound
	}
	return fmt.Sprintf("%s.%s", name, domain), nil
}

func (hipacheRouter) removeElement(name, address string) error {
	conn, err := connect()
	if err != nil {
		return &routeError{"remove", err}
	}
	_, err = conn.Do("LREM", name, 0, address)
	if err != nil {
		return &routeError{"remove", err}
	}
	return nil
}

type routeError struct {
	op  string
	err error
}

func (e *routeError) Error() string {
	return fmt.Sprintf("Could not %s route: %s", e.op, e.err)
}
<|endoftext|>


<commit_before>package sudoku

// Sudoku has one rule - no value can be repeated horizontally, vertically,
// or in the same section. This gives rise to a few simple rules - and those
// rules are applied across each cluster - without even knowning the orientation
// of the cluster.
//
// In order to make solving possible however, that one rule is enforced with
// the following rules:
//
// 1) If all cells are solved, that cluster is solved.
// 2) If any cell is solved, it has no possibles.
// 3) If any cell is solved, that value is not possible in other cells.
// 4) If any cell only has one possible value, that is that cell's value.
// 5) If any x cells only have x possible values, those values are not possible
//  outside of those cells - those values are constrained to those cells.
// 6) If any value only has one possible cell, that is that cell's value.
// 7) If any x values only have x possible cells, those cells only have those
//  possible values - those cells are constrained to those values.
//
// Additional Helper functions are included and explained later.

// indexedCLuster is a datatype for an index for the values of the cluster.
// Each possible value is a key in the map. THe value of each key is an array of
// possible locations for that value - the index and order are not defined,
// instead the values of the array are the indexes of possible cells in for that
// value.
type intArray []int
type indexedCluster map[int]intArray

func indexCluster(in []cell) (out indexedCluster) {
	for id, each := range in {
		for _, onePossible_ := range each.possible {
			out[onePossible] = append(out[onePossible], id)
		}
	}
	return out
}

// This covers rule 1 from above:
// 1) If all cells are solved, that cluster is solved.
func clusterSolved(cluster []cell, u chan cell) (solved bool) {
	solved = true
	for _, each := range cluster {
		if each.actual == 0 {
			solved = false
		} else if len(each.possible) > 0 {
			solved = false
		}
	}
	return solved
}

// This covers rule 2 from above:
// 2) If any cell is solved, it has no possibles.
func solvedNoPossible(cluster []cell, u chan cell) (changed bool) {
	for _, each := range cluster {
		if each.actual == 0 {
			continue
		}
		if len(each.possible) > 0 {
			changed = true
			u <- cell{
				location: each.location,
				possible: each.possible,
			}
		}
	}
	return changed
}

// Removes known values from other possibles in the same cluster
// Covers rule 3 from above:
// 3) If any cell is solved, that value is not possible in other cells.
func eliminateKnowns(workingCluster []cell, u chan cell) (changed bool) {
	var knownValues []int

	// Loop thru and find all solved values.
	for _, each := range workingCluster {
		if each.actual != 0 {
			knownValues = append(knownValues, each.actual)
		}
	}

	for _, each := range workingCluster {
		if anyInArr(each, knownValues) {
			u <- cell{
				location: each.location,
				possible: subArr(each, knownValues)
			}
			changed = true
		}
	}
	return changed
}

// This covers the 4th rule from above:
// 4) If any cell only has one possible value, that is that cell's value.
func singleValueSolver(cluster []cell, u chan cell) (changed bool) {
	for _, each := range cluster {
		// if the cell is already solved, skip this.
		if each.actual != 0 {
			continue
		}

		// if there is more than one possible value for this cell, you should be good
		if len(each.possible) > 1 {
			continue
		}

		// should never happen - probably #TODO# to catch this
		if len(each.possible) < 1 {
			panic("Found an unsolved cell with no possible values")
		}

		changed = true

		// send back an update for this cell
		u <- cell{
			location: each.location,
			actual:   each.possible[0],
			possible: []int{}
		}
	}
	return changed
}

// A helper function to determine the number of valus hit given a specific set
// of cells.
func cellsCost(markedCells map[int]bool, cluster []cell) int {
	var neededValues map[int]bool
	for cellPos, _ := range markedCells {
		for possibleValue, _ := range cluster[cellPos].possible {
			neededValues[possibleValue] = true
		}
	}
	return len(neededValues)
}

func cellLimiterChild(limit int, markedCells map[int]bool, cluster []cell, u chan cell) (changed bool) {
	valueCount := cellsCost(markedCells, cluster)
	// you have overspent - it's a no-go
	if len(markedCells) > limit {
		return false
	}

	// you have room to add more cells (depth first?)
	if len(markedCells) < limit {
		if valueCount < len(markedCells) {
			// #TODO# probably fix this? rework into error?
			panic("less possible values than squares to fill")
		}
		if valueCount > len(markedCells) {
			// you need to try adding each other cell
			for idCell, oneCell := range cluster {
				if oneCell.actual != 0 {
					continue
				}
				if markedCells[idCell] {
					// this cell is already in the map, skip
					continue
				}

				// decend down into looking at that cell
				childMarkedCells := markedCells // #TODO# map copy
				childMarkedCells[idCell] = true
				if cellLimiterChild(limit, childMarkedCells, cluster, u) {
					// if you got true from the child, pass it on
					return true
				}
			}
		}
	}

	// did you fill the cells? if so, mark it
	if valueCount == len(markedCells) {
		// it's a match - so remove values
		for idCell, oneCell := range cluster {
			if oneCell.actual != 0 {
				// already solved
				continue
			}
			if markedCells[idCell] {
				// this cell is a part of the list - no exclusions needed
				continue
			}
			remove := map[int]bool{}
			for potential, _ := range oneCell.possible {
				if markedCells[idCell] {
					// this possibility exists in the map - need to remove it
					remove[potential] = true
				}
			}
			if len(remove) > 0 {
				changed = true
				u <- cell{
					location: oneCell.location,
					possible: remove,
				}
			}
		}
	}
	return changed
}

// This covers rule 5:
// 5) If any x cells have x possible values, those values are not possible
//  outside of those cells - those values are constrained to those cells.
func cellLimiter(cluster []cell, u chan cell) (changed bool) {
	upperBound := len(cluster)
	for _, eachCell := range cluster {
		if eachCell.actual != 0 {
			upperBound--
		}
	}
	for i := 2; i <= upperBound; i++ {
		if cellLimiterChild(i, map[int]bool{}, cluster, u) {
			changed = true
		}
	}
	return changed
}

// This covers rule 6 from above:
// 6) If any value only has one possible cell, that is that cell's value.
func singleCellSolver(index indexedCluster, cluster []cell, u chan cell) (changed bool) {
	for val, section := range index {
		if len(section) < 1 {
			// something went terribly wrong here - #TODO# add panic catch?
			panic("Found an unsolved cell with no possible values")
		} else if len(section) == 1 {
			u <- cell{
				location: cluster[section[0]].location,
				actual:   val,
				possible: []int{},
			}
			changed = true
		}
	}
	return changed
}

// A helper function to determine the number of cells hit by working across a map
// of values.
func valuesCost(markedVals map[int]bool, index indexedCluster, cluseter []cell) int {
	var neededCells map[int]bool
	for value, _ := range markedVals {
		for oneCell, _ := range index[value] {
			neededCells[oneCell] = true
		}
	}
	return len(neededCells)
}

func valueLimiterChild(limit int, markedValues map[int]bool, index indexedCluster,
	cluster []cell, u chan cell) (changed bool) {
	if len(markedValues) > limit {
		// we have marked more values
		return false
	}
	currentCost := valuesCost(markedValues, index, cluster)
	if currentCost > limit {
		// you're over the budget to spend
		return false
	}
	return changed
}

// THis covers rule 7 from above:
// 7) If any x values have only x possible cells, those cells only have those
//  possible values - those cells are constrained to those values.
/*
func valueLimiter(index indexedCluster, cluster []cell, u chan cell) (changed bool) {

}
*/
<commit_msg>rewrote cellsCost to work with arrays instead<commit_after>package sudoku

// Sudoku has one rule - no value can be repeated horizontally, vertically,
// or in the same section. This gives rise to a few simple rules - and those
// rules are applied across each cluster - without even knowning the orientation
// of the cluster.
//
// In order to make solving possible however, that one rule is enforced with
// the following rules:
//
// 1) If all cells are solved, that cluster is solved.
// 2) If any cell is solved, it has no possibles.
// 3) If any cell is solved, that value is not possible in other cells.
// 4) If any cell only has one possible value, that is that cell's value.
// 5) If any x cells only have x possible values, those values are not possible
//  outside of those cells - those values are constrained to those cells.
// 6) If any value only has one possible cell, that is that cell's value.
// 7) If any x values only have x possible cells, those cells only have those
//  possible values - those cells are constrained to those values.
//
// Additional Helper functions are included and explained later.

// indexedCLuster is a datatype for an index for the values of the cluster.
// Each possible value is a key in the map. THe value of each key is an array of
// possible locations for that value - the index and order are not defined,
// instead the values of the array are the indexes of possible cells in for that
// value.
type intArray []int
type indexedCluster map[int]intArray

func indexCluster(in []cell) (out indexedCluster) {
	for id, each := range in {
		for _, onePossible_ := range each.possible {
			out[onePossible] = append(out[onePossible], id)
		}
	}
	return out
}

// This covers rule 1 from above:
// 1) If all cells are solved, that cluster is solved.
func clusterSolved(cluster []cell, u chan cell) (solved bool) {
	solved = true
	for _, each := range cluster {
		if each.actual == 0 {
			solved = false
		} else if len(each.possible) > 0 {
			solved = false
		}
	}
	return solved
}

// This covers rule 2 from above:
// 2) If any cell is solved, it has no possibles.
func solvedNoPossible(cluster []cell, u chan cell) (changed bool) {
	for _, each := range cluster {
		if each.actual == 0 {
			continue
		}
		if len(each.possible) > 0 {
			changed = true
			u <- cell{
				location: each.location,
				possible: each.possible,
			}
		}
	}
	return changed
}

// Removes known values from other possibles in the same cluster
// Covers rule 3 from above:
// 3) If any cell is solved, that value is not possible in other cells.
func eliminateKnowns(workingCluster []cell, u chan cell) (changed bool) {
	var knownValues []int

	// Loop thru and find all solved values.
	for _, each := range workingCluster {
		if each.actual != 0 {
			knownValues = append(knownValues, each.actual)
		}
	}

	for _, each := range workingCluster {
		if anyInArr(each, knownValues) {
			u <- cell{
				location: each.location,
				possible: subArr(each, knownValues)
			}
			changed = true
		}
	}
	return changed
}

// This covers the 4th rule from above:
// 4) If any cell only has one possible value, that is that cell's value.
func singleValueSolver(cluster []cell, u chan cell) (changed bool) {
	for _, each := range cluster {
		// if the cell is already solved, skip this.
		if each.actual != 0 {
			continue
		}

		// if there is more than one possible value for this cell, you should be good
		if len(each.possible) > 1 {
			continue
		}

		// should never happen - probably #TODO# to catch this
		if len(each.possible) < 1 {
			panic("Found an unsolved cell with no possible values")
		}

		changed = true

		// send back an update for this cell
		u <- cell{
			location: each.location,
			actual:   each.possible[0],
			possible: []int{}
		}
	}
	return changed
}

// A helper function to determine the number of valus hit given a specific set
// of cells.
func cellsCost(markedCells []int, cluster []cell) int {
	var neededValues []int
	for _, oneCell := range markedCells {
		neededValues = addArr(neededValues, cluster[oneCell].possible)
	}
	return len(neededValues)
}

func cellLimiterChild(limit int, markedCells map[int]bool, cluster []cell, u chan cell) (changed bool) {
	valueCount := cellsCost(markedCells, cluster)
	// you have overspent - it's a no-go
	if len(markedCells) > limit {
		return false
	}

	// you have room to add more cells (depth first?)
	if len(markedCells) < limit {
		if valueCount < len(markedCells) {
			// #TODO# probably fix this? rework into error?
			panic("less possible values than squares to fill")
		}
		if valueCount > len(markedCells) {
			// you need to try adding each other cell
			for idCell, oneCell := range cluster {
				if oneCell.actual != 0 {
					continue
				}
				if markedCells[idCell] {
					// this cell is already in the map, skip
					continue
				}

				// decend down into looking at that cell
				childMarkedCells := markedCells // #TODO# map copy
				childMarkedCells[idCell] = true
				if cellLimiterChild(limit, childMarkedCells, cluster, u) {
					// if you got true from the child, pass it on
					return true
				}
			}
		}
	}

	// did you fill the cells? if so, mark it
	if valueCount == len(markedCells) {
		// it's a match - so remove values
		for idCell, oneCell := range cluster {
			if oneCell.actual != 0 {
				// already solved
				continue
			}
			if markedCells[idCell] {
				// this cell is a part of the list - no exclusions needed
				continue
			}
			remove := map[int]bool{}
			for potential, _ := range oneCell.possible {
				if markedCells[idCell] {
					// this possibility exists in the map - need to remove it
					remove[potential] = true
				}
			}
			if len(remove) > 0 {
				changed = true
				u <- cell{
					location: oneCell.location,
					possible: remove,
				}
			}
		}
	}
	return changed
}

// This covers rule 5:
// 5) If any x cells have x possible values, those values are not possible
//  outside of those cells - those values are constrained to those cells.
func cellLimiter(cluster []cell, u chan cell) (changed bool) {
	upperBound := len(cluster)
	for _, eachCell := range cluster {
		if eachCell.actual != 0 {
			upperBound--
		}
	}
	for i := 2; i <= upperBound; i++ {
		if cellLimiterChild(i, map[int]bool{}, cluster, u) {
			changed = true
		}
	}
	return changed
}

// This covers rule 6 from above:
// 6) If any value only has one possible cell, that is that cell's value.
func singleCellSolver(index indexedCluster, cluster []cell, u chan cell) (changed bool) {
	for val, section := range index {
		if len(section) < 1 {
			// something went terribly wrong here - #TODO# add panic catch?
			panic("Found an unsolved cell with no possible values")
		} else if len(section) == 1 {
			u <- cell{
				location: cluster[section[0]].location,
				actual:   val,
				possible: []int{},
			}
			changed = true
		}
	}
	return changed
}

// A helper function to determine the number of cells hit by working across a map
// of values.
func valuesCost(markedVals map[int]bool, index indexedCluster, cluseter []cell) int {
	var neededCells map[int]bool
	for value, _ := range markedVals {
		for oneCell, _ := range index[value] {
			neededCells[oneCell] = true
		}
	}
	return len(neededCells)
}

func valueLimiterChild(limit int, markedValues map[int]bool, index indexedCluster,
	cluster []cell, u chan cell) (changed bool) {
	if len(markedValues) > limit {
		// we have marked more values
		return false
	}
	currentCost := valuesCost(markedValues, index, cluster)
	if currentCost > limit {
		// you're over the budget to spend
		return false
	}
	return changed
}

// THis covers rule 7 from above:
// 7) If any x values have only x possible cells, those cells only have those
//  possible values - those cells are constrained to those values.
/*
func valueLimiter(index indexedCluster, cluster []cell, u chan cell) (changed bool) {

}
*/
<|endoftext|>


<commit_before>package hoverfly

import (
	"testing"

	"github.com/SpectoLabs/hoverfly/core/handlers/v1"
	"github.com/SpectoLabs/hoverfly/core/handlers/v2"
	"github.com/SpectoLabs/hoverfly/core/matching"
	"github.com/SpectoLabs/hoverfly/core/models"
	"github.com/SpectoLabs/hoverfly/core/util"
	. "github.com/onsi/gomega"
)

var (
	pairOneRecording = v2.RequestResponsePairView{
		Request: v2.RequestDetailsView{
			RequestType: util.StringToPointer("recording"),
			Destination: util.StringToPointer("test.com"),
			Path:        util.StringToPointer("/testing"),
		},
		Response: v2.ResponseDetailsView{
			Body: "test-body",
		},
	}
)

func TestHoverflyGetSimulationReturnsBlankSimulation_ifThereIsNoData(t *testing.T) {
	RegisterTestingT(t)

	server, unit := testTools(201, `{'message': 'here'}`)
	defer server.Close()

	simulation, err := unit.GetSimulation()
	Expect(err).To(BeNil())

	Expect(simulation.DataView.RequestResponsePairs).To(HaveLen(0))
	Expect(simulation.DataView.GlobalActions.Delays).To(HaveLen(0))

	Expect(simulation.MetaView.SchemaVersion).To(Equal("v1"))
	Expect(simulation.MetaView.HoverflyVersion).To(Equal("v0.9.0"))
	Expect(simulation.MetaView.TimeExported).ToNot(BeNil())
}

func TestHoverfly_GetSimulation_ReturnsASingleRequestResponsePairRecording(t *testing.T) {
	RegisterTestingT(t)

	server, unit := testTools(201, `{'message': 'here'}`)
	defer server.Close()

	recording := models.RequestResponsePair{
		Request: models.RequestDetails{
			Destination: "testhost.com",
			Path:        "/test",
		},
		Response: models.ResponseDetails{
			Status: 200,
			Body:   "test",
		},
	}

	recordingBytes, err := recording.Encode()
	Expect(err).To(BeNil())

	unit.RequestCache.Set([]byte("key"), recordingBytes)

	simulation, err := unit.GetSimulation()
	Expect(err).To(BeNil())

	Expect(simulation.DataView.RequestResponsePairs).To(HaveLen(1))

	Expect(*simulation.DataView.RequestResponsePairs[0].Request.RequestType).To(Equal("recording"))
	Expect(*simulation.DataView.RequestResponsePairs[0].Request.Destination).To(Equal("testhost.com"))
	Expect(*simulation.DataView.RequestResponsePairs[0].Request.Path).To(Equal("/test"))

	Expect(simulation.DataView.RequestResponsePairs[0].Response.Status).To(Equal(200))
	Expect(simulation.DataView.RequestResponsePairs[0].Response.Body).To(Equal("test"))

	Expect(nil).To(BeNil())
}

func TestHoverfly_GetSimulation_ReturnsASingleRequestResponsePairTemplate(t *testing.T) {
	RegisterTestingT(t)

	server, unit := testTools(201, `{'message': 'here'}`)
	defer server.Close()

	unit.RequestMatcher.TemplateStore = append(unit.RequestMatcher.TemplateStore, matching.RequestTemplateResponsePair{
		RequestTemplate: matching.RequestTemplate{
			Destination: util.StringToPointer("test.com"),
		},
		Response: models.ResponseDetails{
			Status: 200,
			Body:   "test-template",
		},
	})

	simulation, err := unit.GetSimulation()
	Expect(err).To(BeNil())

	Expect(simulation.DataView.RequestResponsePairs).To(HaveLen(1))

	Expect(*simulation.DataView.RequestResponsePairs[0].Request.RequestType).To(Equal("template"))
	Expect(*simulation.DataView.RequestResponsePairs[0].Request.Destination).To(Equal("test.com"))
	Expect(simulation.DataView.RequestResponsePairs[0].Request.Path).To(BeNil())
	Expect(simulation.DataView.RequestResponsePairs[0].Request.Method).To(BeNil())
	Expect(simulation.DataView.RequestResponsePairs[0].Request.Query).To(BeNil())
	Expect(simulation.DataView.RequestResponsePairs[0].Request.Scheme).To(BeNil())
	Expect(simulation.DataView.RequestResponsePairs[0].Request.Headers).To(HaveLen(0))

	Expect(simulation.DataView.RequestResponsePairs[0].Response.Status).To(Equal(200))
	Expect(simulation.DataView.RequestResponsePairs[0].Response.EncodedBody).To(BeFalse())
	Expect(simulation.DataView.RequestResponsePairs[0].Response.Body).To(Equal("test-template"))
	Expect(simulation.DataView.RequestResponsePairs[0].Response.Headers).To(HaveLen(0))

	Expect(nil).To(BeNil())
}

func TestHoverflyGetSimulationReturnsMultipleRequestResponsePairs(t *testing.T) {
	RegisterTestingT(t)

	server, unit := testTools(201, `{'message': 'here'}`)
	defer server.Close()

	recording := models.RequestResponsePair{
		Request: models.RequestDetails{
			Destination: "testhost.com",
			Path:        "/test",
		},
		Response: models.ResponseDetails{
			Status: 200,
			Body:   "test",
		},
	}

	recordingBytes, err := recording.Encode()
	Expect(err).To(BeNil())

	unit.RequestCache.Set([]byte("key"), recordingBytes)
	unit.RequestCache.Set([]byte("key2"), recordingBytes)

	simulation, err := unit.GetSimulation()
	Expect(err).To(BeNil())

	Expect(simulation.DataView.RequestResponsePairs).To(HaveLen(2))

	Expect(*simulation.DataView.RequestResponsePairs[0].Request.Destination).To(Equal("testhost.com"))
	Expect(*simulation.DataView.RequestResponsePairs[0].Request.Path).To(Equal("/test"))
	Expect(*simulation.DataView.RequestResponsePairs[0].Request.RequestType).To(Equal("recording"))

	Expect(simulation.DataView.RequestResponsePairs[0].Response.Status).To(Equal(200))
	Expect(simulation.DataView.RequestResponsePairs[0].Response.Body).To(Equal("test"))

	Expect(*simulation.DataView.RequestResponsePairs[1].Request.Destination).To(Equal("testhost.com"))
	Expect(*simulation.DataView.RequestResponsePairs[1].Request.Path).To(Equal("/test"))
	Expect(*simulation.DataView.RequestResponsePairs[1].Request.RequestType).To(Equal("recording"))

	Expect(simulation.DataView.RequestResponsePairs[1].Response.Status).To(Equal(200))
	Expect(simulation.DataView.RequestResponsePairs[1].Response.Body).To(Equal("test"))
}

func TestHoverflyGetSimulationReturnsMultipleDelays(t *testing.T) {
	RegisterTestingT(t)

	server, unit := testTools(201, `{'message': 'here'}`)
	defer server.Close()

	delay1 := models.ResponseDelay{
		UrlPattern: "test-pattern",
		Delay:      100,
	}

	delay2 := models.ResponseDelay{
		HttpMethod: "test",
		Delay:      200,
	}

	responseDelays := models.ResponseDelayList{delay1, delay2}

	unit.ResponseDelays = &responseDelays

	simulation, err := unit.GetSimulation()
	Expect(err).To(BeNil())

	Expect(simulation.DataView.GlobalActions.Delays).To(HaveLen(2))

	Expect(simulation.DataView.GlobalActions.Delays[0].UrlPattern).To(Equal("test-pattern"))
	Expect(simulation.DataView.GlobalActions.Delays[0].HttpMethod).To(Equal(""))
	Expect(simulation.DataView.GlobalActions.Delays[0].Delay).To(Equal(100))

	Expect(simulation.DataView.GlobalActions.Delays[1].UrlPattern).To(Equal(""))
	Expect(simulation.DataView.GlobalActions.Delays[1].HttpMethod).To(Equal("test"))
	Expect(simulation.DataView.GlobalActions.Delays[1].Delay).To(Equal(200))
}

func TestHoverfly_PutSimulation_ImportsRecordings(t *testing.T) {
	RegisterTestingT(t)

	server, unit := testTools(201, `{'message': 'here'}`)
	defer server.Close()

	simulationToImport := v2.SimulationView{
		DataView: v2.DataView{
			RequestResponsePairs: []v2.RequestResponsePairView{pairOneRecording},
			GlobalActions: v2.GlobalActionsView{
				Delays: []v1.ResponseDelayView{},
			},
		},
		MetaView: v2.MetaView{},
	}

	unit.PutSimulation(simulationToImport)

	importedSimulation, err := unit.GetSimulation()
	Expect(err).To(BeNil())

	Expect(importedSimulation).ToNot(BeNil())

	Expect(importedSimulation.RequestResponsePairs).ToNot(BeNil())
	Expect(importedSimulation.RequestResponsePairs).To(HaveLen(1))

	Expect(importedSimulation.RequestResponsePairs[0].Request.RequestType).To(Equal(util.StringToPointer("recording")))
	Expect(importedSimulation.RequestResponsePairs[0].Request.Destination).To(Equal(util.StringToPointer("test.com")))
	Expect(importedSimulation.RequestResponsePairs[0].Request.Path).To(Equal(util.StringToPointer("/testing")))

	Expect(importedSimulation.RequestResponsePairs[0].Response.Body).To(Equal("test-body"))
}
<commit_msg>Added some tests around PutSimulation() for importing recordings and templates<commit_after>package hoverfly

import (
	"testing"

	"github.com/SpectoLabs/hoverfly/core/handlers/v1"
	"github.com/SpectoLabs/hoverfly/core/handlers/v2"
	"github.com/SpectoLabs/hoverfly/core/matching"
	"github.com/SpectoLabs/hoverfly/core/models"
	"github.com/SpectoLabs/hoverfly/core/util"
	. "github.com/onsi/gomega"
)

var (
	pairOneRecording = v2.RequestResponsePairView{
		Request: v2.RequestDetailsView{
			RequestType: util.StringToPointer("recording"),
			Destination: util.StringToPointer("test.com"),
			Path:        util.StringToPointer("/testing"),
		},
		Response: v2.ResponseDetailsView{
			Body: "test-body",
		},
	}

	pairOneTemplate = v2.RequestResponsePairView{
		Request: v2.RequestDetailsView{
			RequestType: util.StringToPointer("template"),
			Path:        util.StringToPointer("/template"),
		},
		Response: v2.ResponseDetailsView{
			Body: "template-body",
		},
	}
)

func TestHoverflyGetSimulationReturnsBlankSimulation_ifThereIsNoData(t *testing.T) {
	RegisterTestingT(t)

	server, unit := testTools(201, `{'message': 'here'}`)
	defer server.Close()

	simulation, err := unit.GetSimulation()
	Expect(err).To(BeNil())

	Expect(simulation.DataView.RequestResponsePairs).To(HaveLen(0))
	Expect(simulation.DataView.GlobalActions.Delays).To(HaveLen(0))

	Expect(simulation.MetaView.SchemaVersion).To(Equal("v1"))
	Expect(simulation.MetaView.HoverflyVersion).To(Equal("v0.9.0"))
	Expect(simulation.MetaView.TimeExported).ToNot(BeNil())
}

func TestHoverfly_GetSimulation_ReturnsASingleRequestResponsePairRecording(t *testing.T) {
	RegisterTestingT(t)

	server, unit := testTools(201, `{'message': 'here'}`)
	defer server.Close()

	recording := models.RequestResponsePair{
		Request: models.RequestDetails{
			Destination: "testhost.com",
			Path:        "/test",
		},
		Response: models.ResponseDetails{
			Status: 200,
			Body:   "test",
		},
	}

	recordingBytes, err := recording.Encode()
	Expect(err).To(BeNil())

	unit.RequestCache.Set([]byte("key"), recordingBytes)

	simulation, err := unit.GetSimulation()
	Expect(err).To(BeNil())

	Expect(simulation.DataView.RequestResponsePairs).To(HaveLen(1))

	Expect(*simulation.DataView.RequestResponsePairs[0].Request.RequestType).To(Equal("recording"))
	Expect(*simulation.DataView.RequestResponsePairs[0].Request.Destination).To(Equal("testhost.com"))
	Expect(*simulation.DataView.RequestResponsePairs[0].Request.Path).To(Equal("/test"))

	Expect(simulation.DataView.RequestResponsePairs[0].Response.Status).To(Equal(200))
	Expect(simulation.DataView.RequestResponsePairs[0].Response.Body).To(Equal("test"))

	Expect(nil).To(BeNil())
}

func TestHoverfly_GetSimulation_ReturnsASingleRequestResponsePairTemplate(t *testing.T) {
	RegisterTestingT(t)

	server, unit := testTools(201, `{'message': 'here'}`)
	defer server.Close()

	unit.RequestMatcher.TemplateStore = append(unit.RequestMatcher.TemplateStore, matching.RequestTemplateResponsePair{
		RequestTemplate: matching.RequestTemplate{
			Destination: util.StringToPointer("test.com"),
		},
		Response: models.ResponseDetails{
			Status: 200,
			Body:   "test-template",
		},
	})

	simulation, err := unit.GetSimulation()
	Expect(err).To(BeNil())

	Expect(simulation.DataView.RequestResponsePairs).To(HaveLen(1))

	Expect(*simulation.DataView.RequestResponsePairs[0].Request.RequestType).To(Equal("template"))
	Expect(*simulation.DataView.RequestResponsePairs[0].Request.Destination).To(Equal("test.com"))
	Expect(simulation.DataView.RequestResponsePairs[0].Request.Path).To(BeNil())
	Expect(simulation.DataView.RequestResponsePairs[0].Request.Method).To(BeNil())
	Expect(simulation.DataView.RequestResponsePairs[0].Request.Query).To(BeNil())
	Expect(simulation.DataView.RequestResponsePairs[0].Request.Scheme).To(BeNil())
	Expect(simulation.DataView.RequestResponsePairs[0].Request.Headers).To(HaveLen(0))

	Expect(simulation.DataView.RequestResponsePairs[0].Response.Status).To(Equal(200))
	Expect(simulation.DataView.RequestResponsePairs[0].Response.EncodedBody).To(BeFalse())
	Expect(simulation.DataView.RequestResponsePairs[0].Response.Body).To(Equal("test-template"))
	Expect(simulation.DataView.RequestResponsePairs[0].Response.Headers).To(HaveLen(0))

	Expect(nil).To(BeNil())
}

func TestHoverflyGetSimulationReturnsMultipleRequestResponsePairs(t *testing.T) {
	RegisterTestingT(t)

	server, unit := testTools(201, `{'message': 'here'}`)
	defer server.Close()

	recording := models.RequestResponsePair{
		Request: models.RequestDetails{
			Destination: "testhost.com",
			Path:        "/test",
		},
		Response: models.ResponseDetails{
			Status: 200,
			Body:   "test",
		},
	}

	recordingBytes, err := recording.Encode()
	Expect(err).To(BeNil())

	unit.RequestCache.Set([]byte("key"), recordingBytes)
	unit.RequestCache.Set([]byte("key2"), recordingBytes)

	simulation, err := unit.GetSimulation()
	Expect(err).To(BeNil())

	Expect(simulation.DataView.RequestResponsePairs).To(HaveLen(2))

	Expect(*simulation.DataView.RequestResponsePairs[0].Request.Destination).To(Equal("testhost.com"))
	Expect(*simulation.DataView.RequestResponsePairs[0].Request.Path).To(Equal("/test"))
	Expect(*simulation.DataView.RequestResponsePairs[0].Request.RequestType).To(Equal("recording"))

	Expect(simulation.DataView.RequestResponsePairs[0].Response.Status).To(Equal(200))
	Expect(simulation.DataView.RequestResponsePairs[0].Response.Body).To(Equal("test"))

	Expect(*simulation.DataView.RequestResponsePairs[1].Request.Destination).To(Equal("testhost.com"))
	Expect(*simulation.DataView.RequestResponsePairs[1].Request.Path).To(Equal("/test"))
	Expect(*simulation.DataView.RequestResponsePairs[1].Request.RequestType).To(Equal("recording"))

	Expect(simulation.DataView.RequestResponsePairs[1].Response.Status).To(Equal(200))
	Expect(simulation.DataView.RequestResponsePairs[1].Response.Body).To(Equal("test"))
}

func TestHoverflyGetSimulationReturnsMultipleDelays(t *testing.T) {
	RegisterTestingT(t)

	server, unit := testTools(201, `{'message': 'here'}`)
	defer server.Close()

	delay1 := models.ResponseDelay{
		UrlPattern: "test-pattern",
		Delay:      100,
	}

	delay2 := models.ResponseDelay{
		HttpMethod: "test",
		Delay:      200,
	}

	responseDelays := models.ResponseDelayList{delay1, delay2}

	unit.ResponseDelays = &responseDelays

	simulation, err := unit.GetSimulation()
	Expect(err).To(BeNil())

	Expect(simulation.DataView.GlobalActions.Delays).To(HaveLen(2))

	Expect(simulation.DataView.GlobalActions.Delays[0].UrlPattern).To(Equal("test-pattern"))
	Expect(simulation.DataView.GlobalActions.Delays[0].HttpMethod).To(Equal(""))
	Expect(simulation.DataView.GlobalActions.Delays[0].Delay).To(Equal(100))

	Expect(simulation.DataView.GlobalActions.Delays[1].UrlPattern).To(Equal(""))
	Expect(simulation.DataView.GlobalActions.Delays[1].HttpMethod).To(Equal("test"))
	Expect(simulation.DataView.GlobalActions.Delays[1].Delay).To(Equal(200))
}

func TestHoverfly_PutSimulation_ImportsRecordings(t *testing.T) {
	RegisterTestingT(t)

	server, unit := testTools(201, `{'message': 'here'}`)
	defer server.Close()

	simulationToImport := v2.SimulationView{
		DataView: v2.DataView{
			RequestResponsePairs: []v2.RequestResponsePairView{pairOneRecording},
			GlobalActions: v2.GlobalActionsView{
				Delays: []v1.ResponseDelayView{},
			},
		},
		MetaView: v2.MetaView{},
	}

	unit.PutSimulation(simulationToImport)

	importedSimulation, err := unit.GetSimulation()
	Expect(err).To(BeNil())

	Expect(importedSimulation).ToNot(BeNil())

	Expect(importedSimulation.RequestResponsePairs).ToNot(BeNil())
	Expect(importedSimulation.RequestResponsePairs).To(HaveLen(1))

	Expect(importedSimulation.RequestResponsePairs[0].Request.RequestType).To(Equal(util.StringToPointer("recording")))
	Expect(importedSimulation.RequestResponsePairs[0].Request.Destination).To(Equal(util.StringToPointer("test.com")))
	Expect(importedSimulation.RequestResponsePairs[0].Request.Path).To(Equal(util.StringToPointer("/testing")))

	Expect(importedSimulation.RequestResponsePairs[0].Response.Body).To(Equal("test-body"))
}

func TestHoverfly_PutSimulation_ImportsTemplates(t *testing.T) {
	RegisterTestingT(t)

	server, unit := testTools(201, `{'message': 'here'}`)
	defer server.Close()

	simulationToImport := v2.SimulationView{
		DataView: v2.DataView{
			RequestResponsePairs: []v2.RequestResponsePairView{pairOneTemplate},
			GlobalActions: v2.GlobalActionsView{
				Delays: []v1.ResponseDelayView{},
			},
		},
		MetaView: v2.MetaView{},
	}

	unit.PutSimulation(simulationToImport)

	importedSimulation, err := unit.GetSimulation()
	Expect(err).To(BeNil())

	Expect(importedSimulation).ToNot(BeNil())

	Expect(importedSimulation.RequestResponsePairs).ToNot(BeNil())
	Expect(importedSimulation.RequestResponsePairs).To(HaveLen(1))

	Expect(importedSimulation.RequestResponsePairs[0].Request.RequestType).To(Equal(util.StringToPointer("template")))
	Expect(importedSimulation.RequestResponsePairs[0].Request.Destination).To(BeNil())
	Expect(importedSimulation.RequestResponsePairs[0].Request.Path).To(Equal(util.StringToPointer("/template")))

	Expect(importedSimulation.RequestResponsePairs[0].Response.Body).To(Equal("template-body"))
}

func TestHoverfly_PutSimulation_ImportsRecordingsAndTemplates(t *testing.T) {
	RegisterTestingT(t)

	server, unit := testTools(201, `{'message': 'here'}`)
	defer server.Close()

	simulationToImport := v2.SimulationView{
		DataView: v2.DataView{
			RequestResponsePairs: []v2.RequestResponsePairView{pairOneRecording, pairOneTemplate},
			GlobalActions: v2.GlobalActionsView{
				Delays: []v1.ResponseDelayView{},
			},
		},
		MetaView: v2.MetaView{},
	}

	unit.PutSimulation(simulationToImport)

	importedSimulation, err := unit.GetSimulation()
	Expect(err).To(BeNil())

	Expect(importedSimulation).ToNot(BeNil())

	Expect(importedSimulation.RequestResponsePairs).ToNot(BeNil())
	Expect(importedSimulation.RequestResponsePairs).To(HaveLen(2))

	Expect(importedSimulation.RequestResponsePairs[0].Request.RequestType).To(Equal(util.StringToPointer("recording")))
	Expect(importedSimulation.RequestResponsePairs[0].Request.Destination).To(Equal(util.StringToPointer("test.com")))
	Expect(importedSimulation.RequestResponsePairs[0].Request.Path).To(Equal(util.StringToPointer("/testing")))

	Expect(importedSimulation.RequestResponsePairs[0].Response.Body).To(Equal("test-body"))

	Expect(importedSimulation.RequestResponsePairs[1].Request.RequestType).To(Equal(util.StringToPointer("template")))
	Expect(importedSimulation.RequestResponsePairs[1].Request.Destination).To(BeNil())
	Expect(importedSimulation.RequestResponsePairs[1].Request.Path).To(Equal(util.StringToPointer("/template")))

	Expect(importedSimulation.RequestResponsePairs[1].Response.Body).To(Equal("template-body"))
}
<|endoftext|>