<commit_before>package matt

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"runtime"
	"strconv"
	"strings"
)

// PDBArg corresponds to an argument to Matt. It can be just a file path
// (represented by the "Location" field), or it can also contain a specific
// chain and a range of residues to align.
type PDBArg struct {
	Location string
	Chain    byte

	// Matt allows one to specify a range of residues to be aligned.
	// If one of ResidueStart or ResidueEnd is specified, BOTH must be specified
	// in addition to a chain. A violation of this invariant will cause 'Run'
	// to panic.
	ResidueStart, ResidueEnd int
}

// DefaultConfig provides some sane defaults to run Matt with. For example:
//
//	results, err := matt.DefaultConfig.Run(pdbArg1, pdbArg2, ...)
var DefaultConfig = Config{
	Binary:       "matt",
	OutputPrefix: "",
	Verbose:      true,
	Vomit:        false,
}

// Config is used to specify the location of the Matt binary in addition to
// any relevant parameters that can be passed to Matt. It also controls the
// level of vomit echoed to stderr.
type Config struct {
	// Binary points to the 'matt' executable. If 'matt' is in your PATH,
	// it is sufficient to leave this as 'matt'.
	Binary string

	// OutputPrefix is the parameter given to '-o' in matt. If left blank,
	// the OutputPrefix will be set to a temporary directory in your system's
	// TempDir.
	OutputPrefix string

	// Verbose controls whether all commands executed are printed to stderr.
	Verbose bool

	// When Vomit is true, all vomit from commands executed will also be
	// printed to stderr.
	Vomit bool
}

// RunAll will execute Matt is parallel over all sets of PDB arguments given.
// The order of execution is unspecified, but the order and length of BOTH of
// the return values []*Results and []error is precisely equivalent to the
// order and length of the input [][]PDBArg.
//
// Namely, for all i in [0, (length of [][]PDBArg) - 2], then either
// ([]Results)[i] is not nil EXCLUSIVE OR ([]error)[i] is not nil.
//
// Since this is meant to batch a lot of calls to Matt, several things are
// forcefully automated for you: 1) The OutputPrefix is forced to be empty,
// which results in temporary directories being created for each Matt
// invocation. 2) After each invocation of Matt, 'CleanDir' is used to clean
// up anything leftover by Matt.
//
// If any particular invocation of Matt fails, it is added to the returned
// error slice, but does not stop the overall execution. To determine whether
// invocation 'i' of Matt failed, simply check if the element at index 'i'
// in the returned error slice is 'nil' or not.
//
// Make sure you have GOMAXPROCS set to an appropriate value, or use
// something like:
//
//	runtime.GOMAXPROCS(runtime.NumCPU())
//
// GOMAXPROCS is the maximum number of CPUs that can be executing
// simultaneously. As of July 10, 2012, this value is set by default to 1.
func (conf Config) RunAll(argsets [][]PDBArg) ([]*Results, []error) {
	type work struct {
		argset []PDBArg
		rchan  chan *Results
		echan  chan error
	}

	// Force the OutputPrefix to be blank so that we use temp directories.
	conf.OutputPrefix = ""

	// The workers that will run matt.
	worker := func(workChan chan work) {
		for work := range workChan {
			res, err := conf.Run(work.argset...)
			res.CleanDir()
			if err != nil {
				work.echan <- err
			} else {
				work.rchan <- &res
			}
		}
	}

	// Start N workers, where N is the number of CPUs.
	workChan := make(chan work, 100)
	for i := 0; i < runtime.NumCPU(); i++ {
		go worker(workChan)
	}

	resultChans := make([]chan *Results, len(argsets))
	errorChans := make([]chan error, len(argsets))
	results := make([]*Results, len(argsets))
	errors := make([]error, len(argsets))
	done := make(chan struct{}, 0)
	go func() {
		for i := range argsets {
			select {
			case res := <-resultChans[i]:
				results[i] = res
				errors[i] = nil
			case err := <-errorChans[i]:
				results[i] = nil
				errors[i] = err
			}
			close(resultChans[i])
			close(errorChans[i])
		}
		done <- struct{}{}
	}()

	for i, argset := range argsets {
		resultChans[i] = make(chan *Results, 1)
		errorChans[i] = make(chan error, 1)
		workChan <- work{
			argset: argset,
			rchan:  resultChans[i],
			echan:  errorChans[i],
		}
	}

	<-done
	close(workChan)
	return results, errors
}

// RunSequence is like RunAll, but each argument set is passed to matt as
// an addition '-o' alignment.

// Run will execute Matt using a particular configuration with a set of
// PDB file arguments. It handles creation of a temporary directory where
// Matt writes its output.
//
// After running matt, if you don't intend to use any of the output files,
// you should call 'Clean' (or 'CleanDir' if you want to remove the parent
// directory containing the Matt output files).
//
// After Matt is executed, the core length, RMSD and p-value are read from
// Matt's output 'txt' file automatically. Thus, it is safe to call 'Clean'
// immediately after running, and still have access to those output values
// via the 'Results' struct.
func (conf Config) Run(pargs ...PDBArg) (Results, error) {
	// If the output prefix is blank, we need to set up a temporary directory
	// and use that as the prefix.
	prefix := conf.OutputPrefix
	if len(prefix) == 0 {
		tempDir, err := ioutil.TempDir("", "gomatt")
		if err != nil { // something very bad has happened
			panic(err)
		}
		prefix = fmt.Sprintf("%s/gomatt", tempDir)
	}

	// Now construct the input PDB args.
	args := []string{"-o", prefix}
	for _, parg := range pargs {
		if len(parg.Location) == 0 {
			panic("A PDB argument must have a non-empty location.")
		}
		switch {
		case parg.ResidueStart > 0 || parg.ResidueEnd > 0:
			// Check the invariants. If ResidueStart or ResidueEnd is set, then
			// both must be set and Chain must not be empty.
			if parg.ResidueStart == 0 ||
				parg.ResidueEnd == 0 ||
				parg.Chain == 0 {

				panic("When either ResidueStart or ResidueEnd is set, then " +
					"both must be set, and the Chain must be set.")
			}
			args = append(args, fmt.Sprintf("%s:%c(%d-%d)",
				parg.Location, parg.Chain, parg.ResidueStart, parg.ResidueEnd))
		case parg.Chain != 0:
			args = append(args, fmt.Sprintf("%s:%c", parg.Location, parg.Chain))
		default:
			args = append(args, parg.Location)
		}
	}

	if conf.Verbose {
		fmt.Fprintf(os.Stderr, "%s %s\n", conf.Binary, strings.Join(args, " "))
	}
	out, err := exec.Command(conf.Binary, args...).CombinedOutput()

	if conf.Vomit {
		fmt.Fprintf(os.Stderr, "%s\n", string(out))
	}
	if err != nil {
		return Results{}, fmt.Errorf("%s\n%s", out, err)
	}
	return newResults(prefix)
}

// Results corresponds to information about Matt's output. Use its methods
// "Fasta", "Pdb", "Spt" and "Txt" to retrieve the file names of each of
// Matt's corresponding output files.
type Results struct {
	prefix     string
	CoreLength int
	RMSD       float64
	Pval       float64
}

// newResults uses the Matt prefix to load several interesting pieces of
// information from Matt's txt output file. Namely, the core length, RMSD
// and p-value. newResults returns an error if the output file cannot be read
// or parsed.
func newResults(prefix string) (Results, error) {
	res := Results{prefix: prefix}

	txtf, err := os.Open(res.Txt())
	if err != nil {
		return res, fmt.Errorf("Could not read Matt's txt output file '%s' "+
			"because: %s.", res.Txt(), err)
	}

	txtb, err := ioutil.ReadAll(txtf)
	if err != nil {
		return res, fmt.Errorf("Could not process Matt's txt output file '%s' "+
			"because: %s.", res.Txt(), err)
	}

	txtLines := strings.Split(strings.TrimSpace(string(txtb)), "\n")
	for _, line := range txtLines {
		coloni := strings.Index(line, ":")
		if coloni == -1 {
			continue
		}
		switch line[:coloni] {
		case "Core Residues":
			numStr := strings.TrimSpace(line[coloni+1:])
			if coreLen, err := strconv.ParseInt(numStr, 10, 32); err == nil {
				res.CoreLength = int(coreLen)
			} else {
				return res,
					fmt.Errorf("Could not find core length in line '%s'.", line)
			}
		case "Core RMSD":
			numStr := strings.TrimSpace(line[coloni+1:])
			if rmsd, err := strconv.ParseFloat(numStr, 64); err == nil {
				res.RMSD = rmsd
			} else {
				return res,
					fmt.Errorf("Could not find RMSD in line '%s'.", line)
			}
		case "P-value":
			numStr := strings.TrimSpace(line[coloni+1:])
			if pval, err := strconv.ParseFloat(numStr, 64); err == nil {
				res.Pval = pval
			} else {
				return res,
					fmt.Errorf("Could not find P-value in line '%s'.", line)
			}
		}
	}
	return res, nil
}

// CleanDir will run Clean and also remove the directory containing the Matt
// files. This is usefull when 'Run' is called with an empty OutputPrefix
// (where a temporary directory is created).
func (res Results) CleanDir() {
	res.Clean()
	os.Remove(path.Dir(res.prefix))
}

// Clean will delete all files produced by matt.
// Namely, 'prefix.{fasta,pdb,spt,txt}'. Errors, if they occur, are suppressed.
func (res Results) Clean() {
	os.Remove(res.Fasta())
	os.Remove(res.Pdb())
	os.Remove(res.Spt())
	os.Remove(res.Txt())
}

// Fasta returns the fasta output file path.
func (res Results) Fasta() string {
	return res.prefix + ".fasta"
}

// Pdb returns the PDB output file path.
func (res Results) Pdb() string {
	return res.prefix + ".pdb"
}

// Spt returns the Spt output file path.
func (res Results) Spt() string {
	return res.prefix + ".spt"
}

// Txt returns the txt output file path.
func (res Results) Txt() string {
	return res.prefix + ".txt"
}
<commit_msg>Hack. PDBArg should be replaced with *pdb.Chain<commit_after>package matt

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"runtime"
	"strconv"
	"strings"
)

// PDBArg corresponds to an argument to Matt. It can be just a file path
// (represented by the "Location" field), or it can also contain a specific
// chain and a range of residues to align.
type PDBArg struct {
	Location string
	IdCode   string
	Chain    byte

	// Matt allows one to specify a range of residues to be aligned.
	// If one of ResidueStart or ResidueEnd is specified, BOTH must be specified
	// in addition to a chain. A violation of this invariant will cause 'Run'
	// to panic.
	ResidueStart, ResidueEnd int
}

// DefaultConfig provides some sane defaults to run Matt with. For example:
//
//	results, err := matt.DefaultConfig.Run(pdbArg1, pdbArg2, ...)
var DefaultConfig = Config{
	Binary:       "matt",
	OutputPrefix: "",
	Verbose:      true,
	Vomit:        false,
}

// Config is used to specify the location of the Matt binary in addition to
// any relevant parameters that can be passed to Matt. It also controls the
// level of vomit echoed to stderr.
type Config struct {
	// Binary points to the 'matt' executable. If 'matt' is in your PATH,
	// it is sufficient to leave this as 'matt'.
	Binary string

	// OutputPrefix is the parameter given to '-o' in matt. If left blank,
	// the OutputPrefix will be set to a temporary directory in your system's
	// TempDir.
	OutputPrefix string

	// Verbose controls whether all commands executed are printed to stderr.
	Verbose bool

	// When Vomit is true, all vomit from commands executed will also be
	// printed to stderr.
	Vomit bool
}

// RunAll will execute Matt is parallel over all sets of PDB arguments given.
// The order of execution is unspecified, but the order and length of BOTH of
// the return values []*Results and []error is precisely equivalent to the
// order and length of the input [][]PDBArg.
//
// Namely, for all i in [0, (length of [][]PDBArg) - 2], then either
// ([]Results)[i] is not nil EXCLUSIVE OR ([]error)[i] is not nil.
//
// Since this is meant to batch a lot of calls to Matt, several things are
// forcefully automated for you: 1) The OutputPrefix is forced to be empty,
// which results in temporary directories being created for each Matt
// invocation. 2) After each invocation of Matt, 'CleanDir' is used to clean
// up anything leftover by Matt.
//
// If any particular invocation of Matt fails, it is added to the returned
// error slice, but does not stop the overall execution. To determine whether
// invocation 'i' of Matt failed, simply check if the element at index 'i'
// in the returned error slice is 'nil' or not.
//
// Make sure you have GOMAXPROCS set to an appropriate value, or use
// something like:
//
//	runtime.GOMAXPROCS(runtime.NumCPU())
//
// GOMAXPROCS is the maximum number of CPUs that can be executing
// simultaneously. As of July 10, 2012, this value is set by default to 1.
func (conf Config) RunAll(argsets [][]PDBArg) ([]*Results, []error) {
	type work struct {
		argset []PDBArg
		rchan  chan *Results
		echan  chan error
	}

	// Force the OutputPrefix to be blank so that we use temp directories.
	conf.OutputPrefix = ""

	// The workers that will run matt.
	worker := func(workChan chan work) {
		for work := range workChan {
			res, err := conf.Run(work.argset...)
			res.CleanDir()
			if err != nil {
				work.echan <- err
			} else {
				work.rchan <- &res
			}
		}
	}

	// Start N workers, where N is the number of CPUs.
	workChan := make(chan work, 100)
	for i := 0; i < runtime.NumCPU(); i++ {
		go worker(workChan)
	}

	resultChans := make([]chan *Results, len(argsets))
	errorChans := make([]chan error, len(argsets))
	results := make([]*Results, len(argsets))
	errors := make([]error, len(argsets))
	done := make(chan struct{}, 0)
	go func() {
		for i := range argsets {
			select {
			case res := <-resultChans[i]:
				results[i] = res
				errors[i] = nil
			case err := <-errorChans[i]:
				results[i] = nil
				errors[i] = err
			}
			close(resultChans[i])
			close(errorChans[i])
		}
		done <- struct{}{}
	}()

	for i, argset := range argsets {
		resultChans[i] = make(chan *Results, 1)
		errorChans[i] = make(chan error, 1)
		workChan <- work{
			argset: argset,
			rchan:  resultChans[i],
			echan:  errorChans[i],
		}
	}

	<-done
	close(workChan)
	return results, errors
}

// Run will execute Matt using a particular configuration with a set of
// PDB file arguments. It handles creation of a temporary directory where
// Matt writes its output.
//
// After running matt, if you don't intend to use any of the output files,
// you should call 'Clean' (or 'CleanDir' if you want to remove the parent
// directory containing the Matt output files).
//
// After Matt is executed, the core length, RMSD and p-value are read from
// Matt's output 'txt' file automatically. Thus, it is safe to call 'Clean'
// immediately after running, and still have access to those output values
// via the 'Results' struct.
func (conf Config) Run(pargs ...PDBArg) (Results, error) {
	// If the output prefix is blank, we need to set up a temporary directory
	// and use that as the prefix.
	prefix := conf.OutputPrefix
	if len(prefix) == 0 {
		tempDir, err := ioutil.TempDir("", "gomatt")
		if err != nil { // something very bad has happened
			panic(err)
		}
		prefix = fmt.Sprintf("%s/gomatt", tempDir)
	}

	// Now construct the input PDB args.
	args := []string{"-o", prefix}
	for _, parg := range pargs {
		if len(parg.Location) == 0 {
			panic("A PDB argument must have a non-empty location.")
		}
		switch {
		case parg.ResidueStart > 0 || parg.ResidueEnd > 0:
			// Check the invariants. If ResidueStart or ResidueEnd is set, then
			// both must be set and Chain must not be empty.
			if parg.ResidueStart == 0 ||
				parg.ResidueEnd == 0 ||
				parg.Chain == 0 {

				panic("When either ResidueStart or ResidueEnd is set, then " +
					"both must be set, and the Chain must be set.")
			}
			args = append(args, fmt.Sprintf("%s:%c(%d-%d)",
				parg.Location, parg.Chain, parg.ResidueStart, parg.ResidueEnd))
		case parg.Chain != 0:
			args = append(args, fmt.Sprintf("%s:%c", parg.Location, parg.Chain))
		default:
			args = append(args, parg.Location)
		}
	}

	if conf.Verbose {
		fmt.Fprintf(os.Stderr, "%s %s\n", conf.Binary, strings.Join(args, " "))
	}
	out, err := exec.Command(conf.Binary, args...).CombinedOutput()

	if conf.Vomit {
		fmt.Fprintf(os.Stderr, "%s\n", string(out))
	}
	if err != nil {
		return Results{}, fmt.Errorf("%s\n%s", out, err)
	}
	return newResults(prefix)
}

// Results corresponds to information about Matt's output. Use its methods
// "Fasta", "Pdb", "Spt" and "Txt" to retrieve the file names of each of
// Matt's corresponding output files.
type Results struct {
	prefix     string
	CoreLength int
	RMSD       float64
	Pval       float64
}

// newResults uses the Matt prefix to load several interesting pieces of
// information from Matt's txt output file. Namely, the core length, RMSD
// and p-value. newResults returns an error if the output file cannot be read
// or parsed.
func newResults(prefix string) (Results, error) {
	res := Results{prefix: prefix}

	txtf, err := os.Open(res.Txt())
	if err != nil {
		return res, fmt.Errorf("Could not read Matt's txt output file '%s' "+
			"because: %s.", res.Txt(), err)
	}

	txtb, err := ioutil.ReadAll(txtf)
	if err != nil {
		return res, fmt.Errorf("Could not process Matt's txt output file '%s' "+
			"because: %s.", res.Txt(), err)
	}

	txtLines := strings.Split(strings.TrimSpace(string(txtb)), "\n")
	for _, line := range txtLines {
		coloni := strings.Index(line, ":")
		if coloni == -1 {
			continue
		}
		switch line[:coloni] {
		case "Core Residues":
			numStr := strings.TrimSpace(line[coloni+1:])
			if coreLen, err := strconv.ParseInt(numStr, 10, 32); err == nil {
				res.CoreLength = int(coreLen)
			} else {
				return res,
					fmt.Errorf("Could not find core length in line '%s'.", line)
			}
		case "Core RMSD":
			numStr := strings.TrimSpace(line[coloni+1:])
			if rmsd, err := strconv.ParseFloat(numStr, 64); err == nil {
				res.RMSD = rmsd
			} else {
				return res,
					fmt.Errorf("Could not find RMSD in line '%s'.", line)
			}
		case "P-value":
			numStr := strings.TrimSpace(line[coloni+1:])
			if pval, err := strconv.ParseFloat(numStr, 64); err == nil {
				res.Pval = pval
			} else {
				return res,
					fmt.Errorf("Could not find P-value in line '%s'.", line)
			}
		}
	}
	return res, nil
}

// CleanDir will run Clean and also remove the directory containing the Matt
// files. This is usefull when 'Run' is called with an empty OutputPrefix
// (where a temporary directory is created).
func (res Results) CleanDir() {
	res.Clean()
	os.Remove(path.Dir(res.prefix))
}

// Clean will delete all files produced by matt.
// Namely, 'prefix.{fasta,pdb,spt,txt}'. Errors, if they occur, are suppressed.
func (res Results) Clean() {
	os.Remove(res.Fasta())
	os.Remove(res.Pdb())
	os.Remove(res.Spt())
	os.Remove(res.Txt())
}

// Fasta returns the fasta output file path.
func (res Results) Fasta() string {
	return res.prefix + ".fasta"
}

// Pdb returns the PDB output file path.
func (res Results) Pdb() string {
	return res.prefix + ".pdb"
}

// Spt returns the Spt output file path.
func (res Results) Spt() string {
	return res.prefix + ".spt"
}

// Txt returns the txt output file path.
func (res Results) Txt() string {
	return res.prefix + ".txt"
}
<|endoftext|>


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

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	v1 "github.com/projectriff/riff/kubernetes-crds/pkg/apis/projectriff.io/v1alpha1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var _ = Describe("Deployer", func() {
	var (
		d deployer
	)

	BeforeEach(func() {
		d = deployer{}
	})

	Describe("buildDeployment", func() {
		var (
			function v1.Function
			link     v1.Link
		)

		BeforeEach(func() {
			function = v1.Function{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "square",
					Namespace: "default",
				},
			}
			link = v1.Link{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "square-link",
					Namespace: "default",
				},
			}
			link.Spec.Windowing.Size = 1
		})

		It("should set the HTTP_PORT var", func() {
			deployment := d.buildDeployment(&link, &function)
			mainContainer := deployment.Spec.Template.Spec.Containers[0]
			Expect(mainContainer.Env).To(ContainElement(corev1.EnvVar{
				Name:  "HTTP_PORT",
				Value: "8080",
			}))
		})

		It("should set the GRPC_PORT var", func() {
			deployment := d.buildDeployment(&link, &function)
			mainContainer := deployment.Spec.Template.Spec.Containers[0]
			Expect(mainContainer.Env).To(ContainElement(corev1.EnvVar{
				Name:  "GRPC_PORT",
				Value: "10382",
			}))
		})

		It("should set the sidecar WINDOWING_STRATEGY var", func() {
			deployment := d.buildDeployment(&link, &function)
			mainContainer := deployment.Spec.Template.Spec.Containers[1]
			Expect(mainContainer.Env).To(ContainElement(corev1.EnvVar{
				Name:  "WINDOWING_STRATEGY",
				Value: "{\"size\":1}",
			}))
		})

		It("creates an owner reference to the link", func() {
			deployment := d.buildDeployment(&link, &function)
			ownerReferences := deployment.OwnerReferences
			Expect(len(ownerReferences)).To(Equal(1))
			Expect(ownerReferences[0].Kind).To(Equal("Link"))
			Expect(ownerReferences[0].Name).To(Equal("square-link"))
			Expect(*ownerReferences[0].Controller).To(BeTrue())
			Expect(*ownerReferences[0].BlockOwnerDeletion).To(BeTrue())
		})

		Context("when the protocol is grpc", func() {

			BeforeEach(func() {
				function.Spec.Protocol = "grpc"
			})

			It("should set the RIFF_FUNCTION_INVOKER_PROTOCOL var to grpc", func() {
				deployment := d.buildDeployment(&link, &function)
				mainContainer := deployment.Spec.Template.Spec.Containers[0]
				Expect(mainContainer.Env).To(ContainElement(corev1.EnvVar{
					Name:  "RIFF_FUNCTION_INVOKER_PROTOCOL",
					Value: "grpc",
				}))
			})

			It("should set the sidecar --protocol and --port arg", func() {
				deployment := d.buildDeployment(&link, &function)
				sidecarContainer := deployment.Spec.Template.Spec.Containers[1]
				args := sidecarContainer.Args
				Expect(args[indexOf(args, "--protocol")+1]).To(Equal("grpc"))
				Expect(args[indexOf(args, "--port")+1]).To(Equal("10382"))
			})

		})

		Context("when the protocol is http", func() {

			BeforeEach(func() {
				function.Spec.Protocol = "http"
			})

			It("should set the RIFF_FUNCTION_INVOKER_PROTOCOL var to http", func() {
				deployment := d.buildDeployment(&link, &function)
				mainContainer := deployment.Spec.Template.Spec.Containers[0]
				Expect(mainContainer.Env).To(ContainElement(corev1.EnvVar{
					Name:  "RIFF_FUNCTION_INVOKER_PROTOCOL",
					Value: "http",
				}))
			})

			It("should set the sidecar --protocol and --port arg", func() {
				deployment := d.buildDeployment(&link, &function)
				sidecarContainer := deployment.Spec.Template.Spec.Containers[1]
				args := sidecarContainer.Args
				Expect(args[indexOf(args, "--protocol")+1]).To(Equal("http"))
				Expect(args[indexOf(args, "--port")+1]).To(Equal("8080"))
			})

		})
	})

})

func indexOf(slice []string, elem string) int {
	for index, item := range slice {
		if item == elem {
			return index
		}
	}
	return -1
}
<commit_msg>Factor out duplication<commit_after>/*
 * Copyright 2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package controller

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	v1 "github.com/projectriff/riff/kubernetes-crds/pkg/apis/projectriff.io/v1alpha1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/api/extensions/v1beta1"
)

var _ = Describe("Deployer", func() {
	var (
		d deployer
	)

	BeforeEach(func() {
		d = deployer{}
	})

	Describe("buildDeployment", func() {
		var (
			function   v1.Function
			link       v1.Link
			deployment v1beta1.Deployment
		)

		BeforeEach(func() {
			function = v1.Function{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "square",
					Namespace: "default",
				},
			}
			link = v1.Link{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "square-link",
					Namespace: "default",
				},
			}
			link.Spec.Windowing.Size = 1
		})

		JustBeforeEach(func() {
			deployment = d.buildDeployment(&link, &function)
		})

		It("should set the HTTP_PORT var", func() {
			mainContainer := deployment.Spec.Template.Spec.Containers[0]
			Expect(mainContainer.Env).To(ContainElement(corev1.EnvVar{
				Name:  "HTTP_PORT",
				Value: "8080",
			}))
		})

		It("should set the GRPC_PORT var", func() {
			mainContainer := deployment.Spec.Template.Spec.Containers[0]
			Expect(mainContainer.Env).To(ContainElement(corev1.EnvVar{
				Name:  "GRPC_PORT",
				Value: "10382",
			}))
		})

		It("should set the sidecar WINDOWING_STRATEGY var", func() {
			mainContainer := deployment.Spec.Template.Spec.Containers[1]
			Expect(mainContainer.Env).To(ContainElement(corev1.EnvVar{
				Name:  "WINDOWING_STRATEGY",
				Value: "{\"size\":1}",
			}))
		})

		It("creates an owner reference to the link", func() {
			ownerReferences := deployment.OwnerReferences
			Expect(len(ownerReferences)).To(Equal(1))
			Expect(ownerReferences[0].Kind).To(Equal("Link"))
			Expect(ownerReferences[0].Name).To(Equal("square-link"))
			Expect(*ownerReferences[0].Controller).To(BeTrue())
			Expect(*ownerReferences[0].BlockOwnerDeletion).To(BeTrue())
		})

		Context("when the protocol is grpc", func() {

			BeforeEach(func() {
				function.Spec.Protocol = "grpc"
			})

			It("should set the RIFF_FUNCTION_INVOKER_PROTOCOL var to grpc", func() {
				mainContainer := deployment.Spec.Template.Spec.Containers[0]
				Expect(mainContainer.Env).To(ContainElement(corev1.EnvVar{
					Name:  "RIFF_FUNCTION_INVOKER_PROTOCOL",
					Value: "grpc",
				}))
			})

			It("should set the sidecar --protocol and --port arg", func() {
				sidecarContainer := deployment.Spec.Template.Spec.Containers[1]
				args := sidecarContainer.Args
				Expect(args[indexOf(args, "--protocol")+1]).To(Equal("grpc"))
				Expect(args[indexOf(args, "--port")+1]).To(Equal("10382"))
			})

		})

		Context("when the protocol is http", func() {

			BeforeEach(func() {
				function.Spec.Protocol = "http"
			})

			It("should set the RIFF_FUNCTION_INVOKER_PROTOCOL var to http", func() {
				mainContainer := deployment.Spec.Template.Spec.Containers[0]
				Expect(mainContainer.Env).To(ContainElement(corev1.EnvVar{
					Name:  "RIFF_FUNCTION_INVOKER_PROTOCOL",
					Value: "http",
				}))
			})

			It("should set the sidecar --protocol and --port arg", func() {
				sidecarContainer := deployment.Spec.Template.Spec.Containers[1]
				args := sidecarContainer.Args
				Expect(args[indexOf(args, "--protocol")+1]).To(Equal("http"))
				Expect(args[indexOf(args, "--port")+1]).To(Equal("8080"))
			})

		})
	})

})

func indexOf(slice []string, elem string) int {
	for index, item := range slice {
		if item == elem {
			return index
		}
	}
	return -1
}
<|endoftext|>


<commit_before>package main

// 命令行解析内容
type Command struct {
	// 是否是帮助的标识
	helpFlag bool
	// 版本标识
	versionFlag bool
	// 类路径
	cpOption string
	// jre路径
	XjreOption string
	// 运行的主类class
	class string
	// Java运行参数
	args []string
}<commit_msg>规范代码格式<commit_after>package main

// 命令行解析内容
type Command struct {
	// 是否是帮助的标识
	helpFlag    bool
	// 版本标识
	versionFlag bool
	// 类路径
	cpOption    string
	// jre路径
	XjreOption  string
	// 运行的主类class
	class       string
	// Java运行参数
	args        []string
}<|endoftext|>


<commit_before>package wmenu

import (
	"fmt"
	"log"
	"os"
	"strings"
	"testing"

	"github.com/dixonwille/wlog"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

var newMenuCases = []string{"Testing this menu.", "", "!@#$%^&*()"}
var setSeperatorCases = []string{"", ".", ",", "~"}
var optionCases = []struct {
	name     string
	def      bool
	function func()
}{
	{"Options", true, func() { fmt.Println("testing option") }},
	{"", false, nil},
}
var actionCases = []func(Option){
	func(opt Option) { fmt.Println(opt) },
	nil,
}
var multipleActionCases = []func([]Option){
	func(opts []Option) { fmt.Println(opts) },
	nil,
}
var addColorCases = []struct {
	opt wlog.Color
	que wlog.Color
	res wlog.Color
	err wlog.Color
}{
	{wlog.None, wlog.None, wlog.None, wlog.None},
	{wlog.Red, wlog.Green, wlog.Blue, wlog.Black},
	{wlog.Yellow, wlog.White, wlog.Cyan, wlog.Magenta},
	{wlog.BrightBlack, wlog.BrightBlue, wlog.BrightCyan, wlog.BrightGreen},
	{wlog.BrightRed, wlog.BrightWhite, wlog.BrightYellow, wlog.BrightMagenta},
}

func Example_simple() {
	reader := strings.NewReader("1\r\n") //Simulates the user typing "1" and hitting the [enter] key
	optFunc := func() {
		fmt.Println("Option 0 was chosen.")
	}
	actFunc := func(opt Option) {
		fmt.Printf("%s has an id of %d.\n", opt.Text, opt.ID)
	}
	menu := NewMenu("Choose an option.")
	menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
	menu.Action(actFunc)
	menu.Option("Option 0", true, optFunc)
	menu.Option("Option 1", false, nil)
	menu.Option("Option 2", true, nil)
	err := menu.Run()
	if err != nil {
		log.Fatal(err)
	}
	//Output:
	//0) Option 0
	//1) Option 1
	//2) Option 2
	//Choose an option.
	//Option 1 has an id of 1.
}

func Example_simpleDefault() {
	reader := strings.NewReader("\r\n") //Simulates the user hitting the [enter] key
	optFunc := func() {
		fmt.Fprint(os.Stdout, "Option 0 was chosen.")
	}
	menu := NewMenu("Choose an option.")
	menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
	menu.Option("Option 0", true, optFunc)
	menu.Option("Option 1", false, nil)
	menu.Option("Option 2", false, nil)
	err := menu.Run()
	if err != nil {
		log.Fatal(err)
	}
	//Output:
	//0) Option 0
	//1) Option 1
	//2) Option 2
	//Choose an option.
	//Option 0 was chosen.
}

func Example_multiple() {
	reader := strings.NewReader("1,2\r\n") //Simulates the user typing "1,2" and hitting the [enter] key
	optFunc := func() {
		fmt.Println("Option 0 was chosen.")
	}
	multiFunc := func(opts []Option) {
		for _, opt := range opts {
			fmt.Printf("%s has an id of %d.\n", opt.Text, opt.ID)
		}
	}
	menu := NewMenu("Choose an option.")
	menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
	menu.MultipleAction(multiFunc)
	menu.SetSeparator(",")
	menu.Option("Option 0", true, optFunc)
	menu.Option("Option 1", false, nil)
	menu.Option("Option 2", true, nil)
	err := menu.Run()
	if err != nil {
		log.Fatal(err)
	}
	//Output:
	//0) Option 0
	//1) Option 1
	//2) Option 2
	//Choose an option.
	//Option 1 has an id of 1.
	//Option 2 has an id of 2.
}

func Example_multipleDefault() {
	reader := strings.NewReader("\r\n") //Simulates the user hitting the [enter] key
	optFunc := func() {
		fmt.Println("Option 0 was chosen.")
	}
	multiFunc := func(opts []Option) {
		for _, opt := range opts {
			fmt.Printf("%s has an id of %d.\n", opt.Text, opt.ID)
		}
	}
	menu := NewMenu("Choose an option.")
	menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
	menu.MultipleAction(multiFunc)
	menu.Option("Option 0", true, optFunc)
	menu.Option("Option 1", false, nil)
	menu.Option("Option 2", true, nil)
	err := menu.Run()
	if err != nil {
		log.Fatal(err)
	}
	//Output:
	//0) Option 0
	//1) Option 1
	//2) Option 2
	//Choose an option.
	//Option 0 has an id of 0.
	//Option 2 has an id of 2.
}

func Example_errorNoResponse() {
	reader := strings.NewReader("\r\n") //Simulates the user hitting the [enter] key
	optFunc := func() {
		fmt.Println("Option 0 was chosen.")
	}
	menu := NewMenu("Choose an option.")
	menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
	menu.Option("Option 0", false, optFunc)
	menu.Option("Option 1", false, nil)
	menu.Option("Option 2", false, nil)
	err := menu.Run()
	if err != nil {
		if IsNoResponseErr(err) {
			fmt.Println("We caught the err: " + err.Error())
		} else {
			log.Fatal(err)
		}
	}
	//Output:
	//0) Option 0
	//1) Option 1
	//2) Option 2
	//Choose an option.
	//We caught the err: No response
}

func Example_errorInvalid() {
	reader := strings.NewReader("3\r\n") //Simulates the user hitting the [enter] key
	optFunc := func() {
		fmt.Println("Option 0 was chosen.")
	}
	menu := NewMenu("Choose an option.")
	menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
	menu.Option("Option 0", false, optFunc)
	menu.Option("Option 1", false, nil)
	menu.Option("Option 2", false, nil)
	err := menu.Run()
	if err != nil {
		if IsInvalidErr(err) {
			fmt.Println("We caught the err: " + err.Error())
		} else {
			log.Fatal(err)
		}
	}
	//Output:
	//0) Option 0
	//1) Option 1
	//2) Option 2
	//Choose an option.
	//We caught the err: Invalid response: 3
}

func Example_errorTooMany() {
	reader := strings.NewReader("1 2\r\n") //Simulates the user hitting the [enter] key
	optFunc := func() {
		fmt.Println("Option 0 was chosen.")
	}
	menu := NewMenu("Choose an option.")
	menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
	menu.Option("Option 0", false, optFunc)
	menu.Option("Option 1", false, nil)
	menu.Option("Option 2", false, nil)
	err := menu.Run()
	if err != nil {
		if IsTooManyErr(err) {
			fmt.Println("We caught the err: " + err.Error())
		} else {
			log.Fatal(err)
		}
	}
	//Output:
	//0) Option 0
	//1) Option 1
	//2) Option 2
	//Choose an option.
	//We caught the err: Too many responses
}

func Example_errorDuplicate() {
	reader := strings.NewReader("1 1\r\n") //Simulates the user hitting the [enter] key
	optFunc := func() {
		fmt.Println("Option 0 was chosen.")
	}
	multiFunc := func(opts []Option) {
		for _, opt := range opts {
			fmt.Printf("%s has an id of %d.\n", opt.Text, opt.ID)
		}
	}
	menu := NewMenu("Choose an option.")
	menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
	menu.MultipleAction(multiFunc)
	menu.Option("Option 0", false, optFunc)
	menu.Option("Option 1", false, nil)
	menu.Option("Option 2", false, nil)
	err := menu.Run()
	if err != nil {
		if IsDuplicateErr(err) {
			fmt.Println("We caught the err: " + err.Error())
		} else {
			log.Fatal(err)
		}
	}
	//Output:
	//0) Option 0
	//1) Option 1
	//2) Option 2
	//Choose an option.
	//We caught the err: Duplicated response: 1

}

func TestNewMenu(t *testing.T) {
	assert := assert.New(t)
	for _, c := range newMenuCases {
		menu := NewMenu(c)
		assert.Equal(c, menu.question)
		assert.Nil(menu.defaultFunction)
		assert.Nil(menu.options)
		assert.Equal(" ", menu.multiSeparator)
		assert.Nil(menu.multiFunction)
		assert.False(menu.loopOnInvalid)
		assert.False(menu.clear)
		assert.NotNil(menu.ui)
	}
}

func TestClearOnMenuRun(t *testing.T) {
	menu := NewMenu("Testing")
	menu.ClearOnMenuRun()
	assert.True(t, menu.clear)
}

func TestSetSeperator(t *testing.T) {
	menu := NewMenu("Testing")
	for _, c := range setSeperatorCases {
		menu.SetSeparator(c)
		assert.Equal(t, c, menu.multiSeparator)
	}
}

func TestLoopOnInvalid(t *testing.T) {
	menu := NewMenu("Testing")
	menu.LoopOnInvalid()
	assert.True(t, menu.loopOnInvalid)
}

func TestOption(t *testing.T) {
	assert := assert.New(t)
	menu := NewMenu("Testing")
	for i, c := range optionCases {
		menu.Option(c.name, c.def, c.function)
		require.Equal(t, i+1, len(menu.options))
		assert.Equal(i, menu.options[i].ID)
		assert.Equal(c.name, menu.options[i].Text)
		assert.Equal(c.def, menu.options[i].isDefault)
		if c.function != nil {
			assert.NotNil(menu.options[i].function)
		} else {
			assert.Nil(menu.options[i].function)
		}
	}
}

func TestAction(t *testing.T) {
	menu := NewMenu("Testing")
	for _, c := range actionCases {
		menu.Action(c)
		if c != nil {
			assert.NotNil(t, menu.defaultFunction)
		} else {
			assert.Nil(t, menu.defaultFunction)
		}
	}
}

func TestMultipleAction(t *testing.T) {
	menu := NewMenu("Testing")
	for _, c := range multipleActionCases {
		menu.MultipleAction(c)
		if c != nil {
			assert.NotNil(t, menu.multiFunction)
		} else {
			assert.Nil(t, menu.multiFunction)
		}
	}
}

func TestAddColor(t *testing.T) {
	menu := NewMenu("Testing")
	for _, c := range addColorCases {
		menu.AddColor(c.opt, c.que, c.res, c.err)
		//Nothing to assert on just make sure the function does not fail
	}
}
<commit_msg>Spread coverage of test<commit_after>package wmenu

import (
	"fmt"
	"log"
	"os"
	"strings"
	"testing"

	"github.com/dixonwille/wlog"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

var newMenuCases = []string{"Testing this menu.", "", "!@#$%^&*()"}
var setSeperatorCases = []string{"", ".", ",", "~"}
var optionCases = []struct {
	name     string
	def      bool
	function func()
}{
	{"Options", true, func() { fmt.Println("testing option") }},
	{"", false, nil},
}
var actionCases = []func(Option){
	func(opt Option) { fmt.Println(opt) },
	nil,
}
var multipleActionCases = []func([]Option){
	func(opts []Option) { fmt.Println(opts) },
	nil,
}
var addColorCases = []struct {
	opt wlog.Color
	que wlog.Color
	res wlog.Color
	err wlog.Color
}{
	{wlog.None, wlog.None, wlog.None, wlog.None},
	{wlog.Red, wlog.Green, wlog.Blue, wlog.Black},
	{wlog.Yellow, wlog.White, wlog.Cyan, wlog.Magenta},
	{wlog.BrightBlack, wlog.BrightBlue, wlog.BrightCyan, wlog.BrightGreen},
	{wlog.BrightRed, wlog.BrightWhite, wlog.BrightYellow, wlog.BrightMagenta},
}

func Example_simple() {
	reader := strings.NewReader("1\r\n") //Simulates the user typing "1" and hitting the [enter] key
	optFunc := func() {
		fmt.Println("Option 0 was chosen.")
	}
	actFunc := func(opt Option) {
		fmt.Printf("%s has an id of %d.\n", opt.Text, opt.ID)
	}
	menu := NewMenu("Choose an option.")
	menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
	menu.Action(actFunc)
	menu.Option("Option 0", true, optFunc)
	menu.Option("Option 1", false, nil)
	menu.Option("Option 2", true, nil)
	err := menu.Run()
	if err != nil {
		log.Fatal(err)
	}
	//Output:
	//0) Option 0
	//1) Option 1
	//2) Option 2
	//Choose an option.
	//Option 1 has an id of 1.
}

func Example_simpleDefault() {
	reader := strings.NewReader("\r\n") //Simulates the user hitting the [enter] key
	optFunc := func() {
		fmt.Fprint(os.Stdout, "Option 0 was chosen.")
	}
	menu := NewMenu("Choose an option.")
	menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
	menu.Option("Option 0", true, optFunc)
	menu.Option("Option 1", false, nil)
	menu.Option("Option 2", false, nil)
	err := menu.Run()
	if err != nil {
		log.Fatal(err)
	}
	//Output:
	//0) Option 0
	//1) Option 1
	//2) Option 2
	//Choose an option.
	//Option 0 was chosen.
}

func Example_multiple() {
	reader := strings.NewReader("1,2\r\n") //Simulates the user typing "1,2" and hitting the [enter] key
	optFunc := func() {
		fmt.Println("Option 0 was chosen.")
	}
	multiFunc := func(opts []Option) {
		for _, opt := range opts {
			fmt.Printf("%s has an id of %d.\n", opt.Text, opt.ID)
		}
	}
	menu := NewMenu("Choose an option.")
	menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
	menu.MultipleAction(multiFunc)
	menu.SetSeparator(",")
	menu.Option("Option 0", true, optFunc)
	menu.Option("Option 1", false, nil)
	menu.Option("Option 2", true, nil)
	err := menu.Run()
	if err != nil {
		log.Fatal(err)
	}
	//Output:
	//0) Option 0
	//1) Option 1
	//2) Option 2
	//Choose an option.
	//Option 1 has an id of 1.
	//Option 2 has an id of 2.
}

func Example_multipleDefault() {
	reader := strings.NewReader("\r\n") //Simulates the user hitting the [enter] key
	optFunc := func() {
		fmt.Println("Option 0 was chosen.")
	}
	multiFunc := func(opts []Option) {
		for _, opt := range opts {
			fmt.Printf("%s has an id of %d.\n", opt.Text, opt.ID)
		}
	}
	menu := NewMenu("Choose an option.")
	menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
	menu.MultipleAction(multiFunc)
	menu.Option("Option 0", true, optFunc)
	menu.Option("Option 1", false, nil)
	menu.Option("Option 2", true, nil)
	err := menu.Run()
	if err != nil {
		log.Fatal(err)
	}
	//Output:
	//0) Option 0
	//1) Option 1
	//2) Option 2
	//Choose an option.
	//Option 0 has an id of 0.
	//Option 2 has an id of 2.
}

func Example_errorNoResponse() {
	reader := strings.NewReader("\r\n") //Simulates the user hitting the [enter] key
	optFunc := func() {
		fmt.Println("Option 0 was chosen.")
	}
	menu := NewMenu("Choose an option.")
	menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
	menu.Option("Option 0", false, optFunc)
	menu.Option("Option 1", false, nil)
	menu.Option("Option 2", false, nil)
	err := menu.Run()
	if err != nil {
		if IsNoResponseErr(err) {
			fmt.Println("We caught the err: " + err.Error())
		} else {
			log.Fatal(err)
		}
	}
	//Output:
	//0) Option 0
	//1) Option 1
	//2) Option 2
	//Choose an option.
	//We caught the err: No response
}

func Example_errorInvalid() {
	reader := strings.NewReader("3\r\n") //Simulates the user typing "3" and hitting the [enter] key
	optFunc := func() {
		fmt.Println("Option 0 was chosen.")
	}
	menu := NewMenu("Choose an option.")
	menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
	menu.Option("Option 0", false, optFunc)
	menu.Option("Option 1", false, nil)
	menu.Option("Option 2", false, nil)
	err := menu.Run()
	if err != nil {
		if IsInvalidErr(err) {
			fmt.Println("We caught the err: " + err.Error())
		} else {
			log.Fatal(err)
		}
	}
	//Output:
	//0) Option 0
	//1) Option 1
	//2) Option 2
	//Choose an option.
	//We caught the err: Invalid response: 3
}

func Example_errorTooMany() {
	reader := strings.NewReader("1 2\r\n") //Simulates the user typing "1 2" and hitting the [enter] key
	optFunc := func() {
		fmt.Println("Option 0 was chosen.")
	}
	menu := NewMenu("Choose an option.")
	menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
	menu.Option("Option 0", false, optFunc)
	menu.Option("Option 1", false, nil)
	menu.Option("Option 2", false, nil)
	err := menu.Run()
	if err != nil {
		if IsTooManyErr(err) {
			fmt.Println("We caught the err: " + err.Error())
		} else {
			log.Fatal(err)
		}
	}
	//Output:
	//0) Option 0
	//1) Option 1
	//2) Option 2
	//Choose an option.
	//We caught the err: Too many responses
}

func Example_errorDuplicate() {
	reader := strings.NewReader("1 1\r\n") //Simulates the user typing "1 1" and hitting the [enter] key
	optFunc := func() {
		fmt.Println("Option 0 was chosen.")
	}
	multiFunc := func(opts []Option) {
		for _, opt := range opts {
			fmt.Printf("%s has an id of %d.\n", opt.Text, opt.ID)
		}
	}
	menu := NewMenu("Choose an option.")
	menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
	menu.MultipleAction(multiFunc)
	menu.Option("Option 0", false, optFunc)
	menu.Option("Option 1", false, nil)
	menu.Option("Option 2", false, nil)
	err := menu.Run()
	if err != nil {
		if IsDuplicateErr(err) {
			fmt.Println("We caught the err: " + err.Error())
		} else {
			log.Fatal(err)
		}
	}
	//Output:
	//0) Option 0
	//1) Option 1
	//2) Option 2
	//Choose an option.
	//We caught the err: Duplicated response: 1

}

func TestNewMenu(t *testing.T) {
	assert := assert.New(t)
	for _, c := range newMenuCases {
		menu := NewMenu(c)
		assert.Equal(c, menu.question)
		assert.Nil(menu.defaultFunction)
		assert.Nil(menu.options)
		assert.Equal(" ", menu.multiSeparator)
		assert.Nil(menu.multiFunction)
		assert.False(menu.loopOnInvalid)
		assert.False(menu.clear)
		assert.NotNil(menu.ui)
	}
}

func TestClearOnMenuRun(t *testing.T) {
	menu := NewMenu("Testing")
	menu.ClearOnMenuRun()
	assert.True(t, menu.clear)
}

func TestSetSeperator(t *testing.T) {
	menu := NewMenu("Testing")
	for _, c := range setSeperatorCases {
		menu.SetSeparator(c)
		assert.Equal(t, c, menu.multiSeparator)
	}
}

func TestLoopOnInvalid(t *testing.T) {
	menu := NewMenu("Testing")
	menu.LoopOnInvalid()
	assert.True(t, menu.loopOnInvalid)
}

func TestOption(t *testing.T) {
	assert := assert.New(t)
	menu := NewMenu("Testing")
	for i, c := range optionCases {
		menu.Option(c.name, c.def, c.function)
		require.Equal(t, i+1, len(menu.options))
		assert.Equal(i, menu.options[i].ID)
		assert.Equal(c.name, menu.options[i].Text)
		assert.Equal(c.def, menu.options[i].isDefault)
		if c.function != nil {
			assert.NotNil(menu.options[i].function)
		} else {
			assert.Nil(menu.options[i].function)
		}
	}
}

func TestAction(t *testing.T) {
	menu := NewMenu("Testing")
	for _, c := range actionCases {
		menu.Action(c)
		if c != nil {
			assert.NotNil(t, menu.defaultFunction)
		} else {
			assert.Nil(t, menu.defaultFunction)
		}
	}
}

func TestMultipleAction(t *testing.T) {
	menu := NewMenu("Testing")
	for _, c := range multipleActionCases {
		menu.MultipleAction(c)
		if c != nil {
			assert.NotNil(t, menu.multiFunction)
		} else {
			assert.Nil(t, menu.multiFunction)
		}
	}
}

func TestAddColor(t *testing.T) {
	menu := NewMenu("Testing")
	for _, c := range addColorCases {
		menu.AddColor(c.opt, c.que, c.res, c.err)
		//Nothing to assert on just make sure the function does not fail
	}
}

func TestClearInAsk(t *testing.T) {
	reader := strings.NewReader("1\r\n") //Simulates the user typing "1" and hitting the [enter] key
	optFunc := func() {
		fmt.Println("Option 0 was chosen.")
	}
	actFunc := func(opt Option) {
		fmt.Printf("%s has an id of %d.\n", opt.Text, opt.ID)
	}
	menu := NewMenu("Choose an option.")
	menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
	menu.Action(actFunc)
	menu.ClearOnMenuRun()
	menu.Option("Option 0", true, optFunc)
	menu.Option("Option 1", false, nil)
	menu.Option("Option 2", true, nil)
	err := menu.Run()
	if err != nil {
		assert.Fail(t, err.Error())
	}
}

func TestDefaultAction(t *testing.T) {
	reader := strings.NewReader("\r\n") //Simulates the user hitting the [enter] key
	optFunc := func() {
		assert.Fail(t, "Should not have called option 0's function")
	}
	actFunc := func(opt Option) {
		assert.Equal(t, -1, opt.ID)
	}
	menu := NewMenu("Choose an option.")
	menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
	menu.Action(actFunc)
	menu.ClearOnMenuRun()
	menu.Option("Option 0", false, optFunc)
	menu.Option("Option 1", false, nil)
	menu.Option("Option 2", false, nil)
	err := menu.Run()
	if err != nil {
		assert.Fail(t, err.Error())
	}
}

func TestDefaultActionWithDefaultOption(t *testing.T) {
	reader := strings.NewReader("\r\n") //Simulates the user hitting the [enter] key
	optFunc := func() {
		assert.Fail(t, "Should not have called option 0's function")
	}
	actFunc := func(opt Option) {
		assert.Equal(t, 1, opt.ID)
		assert.Equal(t, "Option 1", opt.Text)
		assert.Nil(t, opt.function)
		assert.True(t, opt.isDefault)
	}
	menu := NewMenu("Choose an option.")
	menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
	menu.Action(actFunc)
	menu.ClearOnMenuRun()
	menu.Option("Option 0", false, optFunc)
	menu.Option("Option 1", true, nil)
	menu.Option("Option 2", false, nil)
	err := menu.Run()
	if err != nil {
		assert.Fail(t, err.Error())
	}
}

func TestOptionsFunction(t *testing.T) {
	reader := strings.NewReader("0\r\n") //Simulates the user typing "0" and hitting the [enter] key
	optFunc := func() {
	}
	actFunc := func(opt Option) {
		assert.Fail(t, "Should not have called the menu's default function")
	}
	menu := NewMenu("Choose an option.")
	menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
	menu.Action(actFunc)
	menu.ClearOnMenuRun()
	menu.Option("Option 0", false, optFunc)
	menu.Option("Option 1", true, nil)
	menu.Option("Option 2", false, nil)
	err := menu.Run()
	if err != nil {
		assert.Fail(t, err.Error())
	}
}

func TestWlogAskErr(t *testing.T) {
	reader := strings.NewReader("1") //Simulates the user typing "1" without hitting [enter]. Can't happen when reader is os.Stdin
	optFunc := func() {
		assert.Fail(t, "Should not have called option 0's function")
	}
	actFunc := func(opt Option) {
		assert.Fail(t, "Should not have called the menu's default function")
	}
	menu := NewMenu("Choose an option.")
	menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
	menu.Action(actFunc)
	menu.ClearOnMenuRun()
	menu.Option("Option 0", false, optFunc)
	menu.Option("Option 1", true, nil)
	menu.Option("Option 2", false, nil)
	err := menu.Run()
	if err != nil {
		assert.Equal(t, "EOF", err.Error())
	}
}

func TestLetterForResponse(t *testing.T) {
	reader := strings.NewReader("a\r\n") //Simulates the user typing "a" and hitting [enter].
	optFunc := func() {
		assert.Fail(t, "Should not have called option 0's function")
	}
	actFunc := func(opt Option) {
		assert.Fail(t, "Should not have called the menu's default function")
	}
	menu := NewMenu("Choose an option.")
	menu.ChangeReaderWriter(reader, os.Stdout, os.Stderr)
	menu.Action(actFunc)
	menu.ClearOnMenuRun()
	menu.Option("Option 0", false, optFunc)
	menu.Option("Option 1", true, nil)
	menu.Option("Option 2", false, nil)
	err := menu.Run()
	if err != nil {
		require.True(t, IsInvalidErr(err))
		e := err.(*MenuError)
		assert.Equal(t, "a", e.Res)
	}
}
<|endoftext|>


<commit_before>package excelize

import (
	"encoding/json"
	"encoding/xml"
	"fmt"
	"strconv"
	"strings"
)

// parseFormatCommentsSet provides function to parse the format settings of the
// comment with default value.
func parseFormatCommentsSet(formatSet string) (*formatComment, error) {
	format := formatComment{
		Author: "Author:",
		Text:   " ",
	}
	err := json.Unmarshal([]byte(formatSet), &format)
	return &format, err
}

// AddComment provides the method to add comment in a sheet by given worksheet
// index, cell and format set (such as author and text). Note that the max
// author length is 255 and the max text length is 32512. For example, add a
// comment in Sheet1!$A$30:
//
//    xlsx.AddComment("Sheet1", "A30", `{"author":"Excelize: ","text":"This is a comment."}`)
//
func (f *File) AddComment(sheet, cell, format string) error {
	formatSet, err := parseFormatCommentsSet(format)
	if err != nil {
		return err
	}
	// Read sheet data.
	xlsx := f.workSheetReader(sheet)
	commentID := f.countComments() + 1
	drawingVML := "xl/drawings/vmlDrawing" + strconv.Itoa(commentID) + ".vml"
	sheetRelationshipsComments := "../comments" + strconv.Itoa(commentID) + ".xml"
	sheetRelationshipsDrawingVML := "../drawings/vmlDrawing" + strconv.Itoa(commentID) + ".vml"
	if xlsx.LegacyDrawing != nil {
		// The worksheet already has a comments relationships, use the relationships drawing ../drawings/vmlDrawing%d.vml.
		sheetRelationshipsDrawingVML = f.getSheetRelationshipsTargetByID(sheet, xlsx.LegacyDrawing.RID)
		commentID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingVML, "../drawings/vmlDrawing"), ".vml"))
		drawingVML = strings.Replace(sheetRelationshipsDrawingVML, "..", "xl", -1)
	} else {
		// Add first comment for given sheet.
		rID := f.addSheetRelationships(sheet, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "")
		f.addSheetRelationships(sheet, SourceRelationshipComments, sheetRelationshipsComments, "")
		f.addSheetLegacyDrawing(sheet, rID)
	}
	commentsXML := "xl/comments" + strconv.Itoa(commentID) + ".xml"
	f.addComment(commentsXML, cell, formatSet)
	var colCount int
	for i, l := range strings.Split(formatSet.Text, "\n") {
		if ll := len(l); ll > colCount {
			if i == 0 {
				ll += len(formatSet.Author)
			}
			colCount = ll
		}
	}
	f.addDrawingVML(commentID, drawingVML, cell, strings.Count(formatSet.Text, "\n")+1, colCount)
	f.addContentTypePart(commentID, "comments")
	return err
}

// addDrawingVML provides function to create comment as
// xl/drawings/vmlDrawing%d.vml by given commit ID and cell.
func (f *File) addDrawingVML(commentID int, drawingVML, cell string, lineCount, colCount int) {
	col := string(strings.Map(letterOnlyMapF, cell))
	row, _ := strconv.Atoi(strings.Map(intOnlyMapF, cell))
	xAxis := row - 1
	yAxis := TitleToNumber(col)
	vml := vmlDrawing{
		XMLNSv:  "urn:schemas-microsoft-com:vml",
		XMLNSo:  "urn:schemas-microsoft-com:office:office",
		XMLNSx:  "urn:schemas-microsoft-com:office:excel",
		XMLNSmv: "http://macVmlSchemaUri",
		Shapelayout: &xlsxShapelayout{
			Ext: "edit",
			IDmap: &xlsxIDmap{
				Ext:  "edit",
				Data: commentID,
			},
		},
		Shapetype: &xlsxShapetype{
			ID:        "_x0000_t202",
			Coordsize: "21600,21600",
			Spt:       202,
			Path:      "m0,0l0,21600,21600,21600,21600,0xe",
			Stroke: &xlsxStroke{
				Joinstyle: "miter",
			},
			VPath: &vPath{
				Gradientshapeok: "t",
				Connecttype:     "miter",
			},
		},
	}
	sp := encodeShape{
		Fill: &vFill{
			Color2: "#fbfe82",
			Angle:  -180,
			Type:   "gradient",
			Fill: &oFill{
				Ext:  "view",
				Type: "gradientUnscaled",
			},
		},
		Shadow: &vShadow{
			On:       "t",
			Color:    "black",
			Obscured: "t",
		},
		Path: &vPath{
			Connecttype: "none",
		},
		Textbox: &vTextbox{
			Style: "mso-direction-alt:auto",
			Div: &xlsxDiv{
				Style: "text-align:left",
			},
		},
		ClientData: &xClientData{
			ObjectType: "Note",
			Anchor: fmt.Sprintf(
				"%d, 23, %d, 0, %d, %d, %d, 5",
				1+yAxis, 1+xAxis, 1+yAxis+lineCount, (colCount*991)/175+5, 2+xAxis),
			AutoFill: "True",
			Row:      xAxis,
			Column:   yAxis,
		},
	}
	s, _ := xml.Marshal(sp)
	shape := xlsxShape{
		ID:          "_x0000_s1025",
		Type:        "#_x0000_t202",
		Style:       "position:absolute;73.5pt;width:108pt;height:59.25pt;z-index:1;visibility:hidden",
		Fillcolor:   "#fbf6d6",
		Strokecolor: "#edeaa1",
		Val:         string(s[13 : len(s)-14]),
	}
	c, ok := f.XLSX[drawingVML]
	if ok {
		d := decodeVmlDrawing{}
		_ = xml.Unmarshal([]byte(c), &d)
		for _, v := range d.Shape {
			s := xlsxShape{
				ID:          "_x0000_s1025",
				Type:        "#_x0000_t202",
				Style:       "position:absolute;73.5pt;width:108pt;height:59.25pt;z-index:1;visibility:hidden",
				Fillcolor:   "#fbf6d6",
				Strokecolor: "#edeaa1",
				Val:         v.Val,
			}
			vml.Shape = append(vml.Shape, s)
		}
	}
	vml.Shape = append(vml.Shape, shape)
	v, _ := xml.Marshal(vml)
	f.XLSX[drawingVML] = v
}

// addComment provides function to create chart as xl/comments%d.xml by given
// cell and format sets.
func (f *File) addComment(commentsXML, cell string, formatSet *formatComment) {
	a := formatSet.Author
	t := formatSet.Text
	if len(a) > 255 {
		a = a[0:255]
	}
	if len(t) > 32512 {
		t = t[0:32512]
	}
	comments := xlsxComments{
		Authors: []xlsxAuthor{
			{
				Author: formatSet.Author,
			},
		},
	}
	cmt := xlsxComment{
		Ref:      cell,
		AuthorID: 0,
		Text: xlsxText{
			R: []xlsxR{
				{
					RPr: &xlsxRPr{
						B:  " ",
						Sz: &attrValFloat{Val: 9},
						Color: &xlsxColor{
							Indexed: 81,
						},
						RFont:  &attrValString{Val: "Calibri"},
						Family: &attrValInt{Val: 2},
					},
					T: a,
				},
				{
					RPr: &xlsxRPr{
						Sz: &attrValFloat{Val: 9},
						Color: &xlsxColor{
							Indexed: 81,
						},
						RFont:  &attrValString{Val: "Calibri"},
						Family: &attrValInt{Val: 2},
					},
					T: t,
				},
			},
		},
	}
	c, ok := f.XLSX[commentsXML]
	if ok {
		d := xlsxComments{}
		_ = xml.Unmarshal([]byte(c), &d)
		comments.CommentList.Comment = append(comments.CommentList.Comment, d.CommentList.Comment...)
	}
	comments.CommentList.Comment = append(comments.CommentList.Comment, cmt)
	v, _ := xml.Marshal(comments)
	f.saveFileList(commentsXML, v)
}

// countComments provides function to get comments files count storage in the
// folder xl.
func (f *File) countComments() int {
	count := 0
	for k := range f.XLSX {
		if strings.Contains(k, "xl/comments") {
			count++
		}
	}
	return count
}
<commit_msg>Update comment.go<commit_after>package excelize

import (
	"encoding/json"
	"encoding/xml"
	"fmt"
	"strconv"
	"strings"
)

// parseFormatCommentsSet provides function to parse the format settings of the
// comment with default value.
func parseFormatCommentsSet(formatSet string) (*formatComment, error) {
	format := formatComment{
		Author: "Author:",
		Text:   " ",
	}
	err := json.Unmarshal([]byte(formatSet), &format)
	return &format, err
}

// AddComment provides the method to add comment in a sheet by given worksheet
// index, cell and format set (such as author and text). Note that the max
// author length is 255 and the max text length is 32512. For example, add a
// comment in Sheet1!$A$30:
//
//    xlsx.AddComment("Sheet1", "A30", `{"author":"Excelize: ","text":"This is a comment."}`)
//
func (f *File) AddComment(sheet, cell, format string) error {
	formatSet, err := parseFormatCommentsSet(format)
	if err != nil {
		return err
	}
	// Read sheet data.
	xlsx := f.workSheetReader(sheet)
	commentID := f.countComments() + 1
	drawingVML := "xl/drawings/vmlDrawing" + strconv.Itoa(commentID) + ".vml"
	sheetRelationshipsComments := "../comments" + strconv.Itoa(commentID) + ".xml"
	sheetRelationshipsDrawingVML := "../drawings/vmlDrawing" + strconv.Itoa(commentID) + ".vml"
	if xlsx.LegacyDrawing != nil {
		// The worksheet already has a comments relationships, use the relationships drawing ../drawings/vmlDrawing%d.vml.
		sheetRelationshipsDrawingVML = f.getSheetRelationshipsTargetByID(sheet, xlsx.LegacyDrawing.RID)
		commentID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingVML, "../drawings/vmlDrawing"), ".vml"))
		drawingVML = strings.Replace(sheetRelationshipsDrawingVML, "..", "xl", -1)
	} else {
		// Add first comment for given sheet.
		rID := f.addSheetRelationships(sheet, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "")
		f.addSheetRelationships(sheet, SourceRelationshipComments, sheetRelationshipsComments, "")
		f.addSheetLegacyDrawing(sheet, rID)
	}
	commentsXML := "xl/comments" + strconv.Itoa(commentID) + ".xml"
	f.addComment(commentsXML, cell, formatSet)
	var colCount int
	for i, l := range strings.Split(formatSet.Text, "\n") {
		if ll := len(l); ll > colCount {
			if i == 0 {
				ll += len(formatSet.Author)
			}
			colCount = ll
		}
	}
	f.addDrawingVML(commentID, drawingVML, cell, strings.Count(formatSet.Text, "\n")+1, colCount)
	f.addContentTypePart(commentID, "comments")
	return err
}

// addDrawingVML provides function to create comment as
// xl/drawings/vmlDrawing%d.vml by given commit ID and cell.
func (f *File) addDrawingVML(commentID int, drawingVML, cell string, lineCount, colCount int) {
	col := string(strings.Map(letterOnlyMapF, cell))
	row, _ := strconv.Atoi(strings.Map(intOnlyMapF, cell))
	xAxis := row - 1
	yAxis := TitleToNumber(col)
	vml := vmlDrawing{
		XMLNSv:  "urn:schemas-microsoft-com:vml",
		XMLNSo:  "urn:schemas-microsoft-com:office:office",
		XMLNSx:  "urn:schemas-microsoft-com:office:excel",
		XMLNSmv: "http://macVmlSchemaUri",
		Shapelayout: &xlsxShapelayout{
			Ext: "edit",
			IDmap: &xlsxIDmap{
				Ext:  "edit",
				Data: commentID,
			},
		},
		Shapetype: &xlsxShapetype{
			ID:        "_x0000_t202",
			Coordsize: "21600,21600",
			Spt:       202,
			Path:      "m0,0l0,21600,21600,21600,21600,0xe",
			Stroke: &xlsxStroke{
				Joinstyle: "miter",
			},
			VPath: &vPath{
				Gradientshapeok: "t",
				Connecttype:     "miter",
			},
		},
	}
	sp := encodeShape{
		Fill: &vFill{
			Color2: "#fbfe82",
			Angle:  -180,
			Type:   "gradient",
			Fill: &oFill{
				Ext:  "view",
				Type: "gradientUnscaled",
			},
		},
		Shadow: &vShadow{
			On:       "t",
			Color:    "black",
			Obscured: "t",
		},
		Path: &vPath{
			Connecttype: "none",
		},
		Textbox: &vTextbox{
			Style: "mso-direction-alt:auto",
			Div: &xlsxDiv{
				Style: "text-align:left",
			},
		},
		ClientData: &xClientData{
			ObjectType: "Note",
			Anchor: fmt.Sprintf(
				"%d, 23, %d, 0, %d, %d, %d, 5",
				1+yAxis, 1+xAxis, 2+yAxis+lineCount, colCount+yAxis, 2+xAxis+lineCount),
			AutoFill: "True",
			Row:      xAxis,
			Column:   yAxis,
		},
	}
	s, _ := xml.Marshal(sp)
	shape := xlsxShape{
		ID:          "_x0000_s1025",
		Type:        "#_x0000_t202",
		Style:       "position:absolute;73.5pt;width:108pt;height:59.25pt;z-index:1;visibility:hidden",
		Fillcolor:   "#fbf6d6",
		Strokecolor: "#edeaa1",
		Val:         string(s[13 : len(s)-14]),
	}
	c, ok := f.XLSX[drawingVML]
	if ok {
		d := decodeVmlDrawing{}
		_ = xml.Unmarshal([]byte(c), &d)
		for _, v := range d.Shape {
			s := xlsxShape{
				ID:          "_x0000_s1025",
				Type:        "#_x0000_t202",
				Style:       "position:absolute;73.5pt;width:108pt;height:59.25pt;z-index:1;visibility:hidden",
				Fillcolor:   "#fbf6d6",
				Strokecolor: "#edeaa1",
				Val:         v.Val,
			}
			vml.Shape = append(vml.Shape, s)
		}
	}
	vml.Shape = append(vml.Shape, shape)
	v, _ := xml.Marshal(vml)
	f.XLSX[drawingVML] = v
}

// addComment provides function to create chart as xl/comments%d.xml by given
// cell and format sets.
func (f *File) addComment(commentsXML, cell string, formatSet *formatComment) {
	a := formatSet.Author
	t := formatSet.Text
	if len(a) > 255 {
		a = a[0:255]
	}
	if len(t) > 32512 {
		t = t[0:32512]
	}
	comments := xlsxComments{
		Authors: []xlsxAuthor{
			{
				Author: formatSet.Author,
			},
		},
	}
	cmt := xlsxComment{
		Ref:      cell,
		AuthorID: 0,
		Text: xlsxText{
			R: []xlsxR{
				{
					RPr: &xlsxRPr{
						B:  " ",
						Sz: &attrValFloat{Val: 9},
						Color: &xlsxColor{
							Indexed: 81,
						},
						RFont:  &attrValString{Val: "Calibri"},
						Family: &attrValInt{Val: 2},
					},
					T: a,
				},
				{
					RPr: &xlsxRPr{
						Sz: &attrValFloat{Val: 9},
						Color: &xlsxColor{
							Indexed: 81,
						},
						RFont:  &attrValString{Val: "Calibri"},
						Family: &attrValInt{Val: 2},
					},
					T: t,
				},
			},
		},
	}
	c, ok := f.XLSX[commentsXML]
	if ok {
		d := xlsxComments{}
		_ = xml.Unmarshal([]byte(c), &d)
		comments.CommentList.Comment = append(comments.CommentList.Comment, d.CommentList.Comment...)
	}
	comments.CommentList.Comment = append(comments.CommentList.Comment, cmt)
	v, _ := xml.Marshal(comments)
	f.saveFileList(commentsXML, v)
}

// countComments provides function to get comments files count storage in the
// folder xl.
func (f *File) countComments() int {
	count := 0
	for k := range f.XLSX {
		if strings.Contains(k, "xl/comments") {
			count++
		}
	}
	return count
}
<|endoftext|>


<commit_before>package vega

import (
	"sync"
	"time"

	"github.com/vektra/errors"
)

type clusterNode struct {
	lock   sync.Mutex
	router *Router
	local  *Registry
	disk   *diskStorage

	subscriptions []*Subscription
}

func NewClusterNode(path string, router *Router) (*clusterNode, error) {
	disk, err := NewDiskStorage(path)
	if err != nil {
		return nil, err
	}

	return &clusterNode{
		disk:   disk,
		local:  NewRegistry(disk.Mailbox),
		router: router,
	}, nil
}

func NewMemClusterNode(path string) (*clusterNode, error) {
	return NewClusterNode(path, MemRouter())
}

func (cn *clusterNode) Registry() *Registry {
	return cn.local
}

func (cn *clusterNode) AddRoute(name string, s Storage) {
	cn.router.Add(name, s)
}

func (cn *clusterNode) Close() error {
	return cn.disk.Close()
}

func (cn *clusterNode) Declare(name string) error {
	cn.local.Declare(name)
	cn.router.Add(name, cn.local)
	return nil
}

func (cn *clusterNode) Abandon(name string) error {
	cn.local.Abandon(name)
	return cn.router.Remove(name)
}

type publishedPusher struct {
	*clusterNode
}

func (pp *publishedPusher) Push(name string, msg *Message) error {
	debugf("remote publish received!\n")
	return pp.publishLocally(msg)
}

func (cn *clusterNode) subscribe(msg *Message) error {
	cn.lock.Lock()
	defer cn.lock.Unlock()

	debugf("doing subscribe...\n")

	cn.router.Add(":publish", &publishedPusher{cn})

	sub := ParseSubscription(msg.CorrelationId)
	sub.Mailbox = msg.ReplyTo

	cn.subscriptions = append(cn.subscriptions, sub)

	return nil
}

func (cn *clusterNode) publishLocally(msg *Message) error {
	cn.lock.Lock()
	defer cn.lock.Unlock()

	for _, sub := range cn.subscriptions {
		if sub.Match(msg.CorrelationId) {
			cn.router.Push(sub.Mailbox, msg)
		}
	}

	return nil
}

func (cn *clusterNode) publish(msg *Message) error {
	debugf("performing publish\n")
	err := cn.publishLocally(msg)
	if err != nil {
		return errors.Context(err, "publishLocally")
	}

	return cn.router.Push(":publish", msg)
}

func (cn *clusterNode) Push(name string, msg *Message) error {
	switch name {
	case ":subscribe":
		return cn.subscribe(msg)
	case ":publish":
		return cn.publish(msg)
	default:
		return cn.router.Push(name, msg)
	}
}

func (cn *clusterNode) Poll(name string) (*Delivery, error) {
	return cn.local.Poll(name)
}

func (cn *clusterNode) LongPoll(name string, til time.Duration) (*Delivery, error) {
	return cn.local.LongPoll(name, til)
}

func (cn *clusterNode) LongPollCancelable(name string, til time.Duration, done chan struct{}) (*Delivery, error) {
	return cn.local.LongPollCancelable(name, til, done)
}
<commit_msg>Only initialize the :publish route once<commit_after>package vega

import (
	"sync"
	"time"

	"github.com/vektra/errors"
)

type clusterNode struct {
	lock   sync.Mutex
	router *Router
	local  *Registry
	disk   *diskStorage

	setupSubscriber bool
	subscriptions   []*Subscription
}

func NewClusterNode(path string, router *Router) (*clusterNode, error) {
	disk, err := NewDiskStorage(path)
	if err != nil {
		return nil, err
	}

	return &clusterNode{
		disk:   disk,
		local:  NewRegistry(disk.Mailbox),
		router: router,
	}, nil
}

func NewMemClusterNode(path string) (*clusterNode, error) {
	return NewClusterNode(path, MemRouter())
}

func (cn *clusterNode) Registry() *Registry {
	return cn.local
}

func (cn *clusterNode) AddRoute(name string, s Storage) {
	cn.router.Add(name, s)
}

func (cn *clusterNode) Close() error {
	return cn.disk.Close()
}

func (cn *clusterNode) Declare(name string) error {
	cn.local.Declare(name)
	cn.router.Add(name, cn.local)
	return nil
}

func (cn *clusterNode) Abandon(name string) error {
	cn.local.Abandon(name)
	return cn.router.Remove(name)
}

type publishedPusher struct {
	*clusterNode
}

func (pp *publishedPusher) Push(name string, msg *Message) error {
	debugf("remote publish received!\n")
	return pp.publishLocally(msg)
}

func (cn *clusterNode) subscribe(msg *Message) error {
	cn.lock.Lock()
	defer cn.lock.Unlock()

	debugf("doing subscribe...\n")

	if !cn.setupSubscriber {
		cn.router.Add(":publish", &publishedPusher{cn})
		cn.setupSubscriber = true
	}

	sub := ParseSubscription(msg.CorrelationId)
	sub.Mailbox = msg.ReplyTo

	cn.subscriptions = append(cn.subscriptions, sub)

	return nil
}

func (cn *clusterNode) publishLocally(msg *Message) error {
	cn.lock.Lock()
	defer cn.lock.Unlock()

	for _, sub := range cn.subscriptions {
		if sub.Match(msg.CorrelationId) {
			cn.router.Push(sub.Mailbox, msg)
		}
	}

	return nil
}

func (cn *clusterNode) publish(msg *Message) error {
	debugf("performing publish\n")
	err := cn.publishLocally(msg)
	if err != nil {
		return errors.Context(err, "publishLocally")
	}

	return cn.router.Push(":publish", msg)
}

func (cn *clusterNode) Push(name string, msg *Message) error {
	switch name {
	case ":subscribe":
		return cn.subscribe(msg)
	case ":publish":
		return cn.publish(msg)
	default:
		return cn.router.Push(name, msg)
	}
}

func (cn *clusterNode) Poll(name string) (*Delivery, error) {
	return cn.local.Poll(name)
}

func (cn *clusterNode) LongPoll(name string, til time.Duration) (*Delivery, error) {
	return cn.local.LongPoll(name, til)
}

func (cn *clusterNode) LongPollCancelable(name string, til time.Duration, done chan struct{}) (*Delivery, error) {
	return cn.local.LongPollCancelable(name, til, done)
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2016 Red Hat, Inc.
 *
 * 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 ofthe 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 specificlanguage governing permissions and
 * limitations under the License.
 *
 */

package topology

import (
	"fmt"

	uuid "github.com/nu7hatch/gouuid"
	"github.com/skydive-project/skydive/common"
	"github.com/skydive-project/skydive/graffiti/graph"
	"github.com/skydive-project/skydive/logging"
)

// ErrNetworkPathNotFound
const (
	OwnershipLink = "ownership"
	Layer2Link    = "layer2"
)

var (
	// ErrNoPathToHost is called when no host could be found as the parent of a node
	ErrNoPathToHost = func(name string) error { return fmt.Errorf("Failed to determine network namespace path for %s", name) }
)

// OwnershipMetadata returns metadata for an ownership link
func OwnershipMetadata() graph.Metadata {
	return graph.Metadata{"RelationType": OwnershipLink}
}

// Layer2Metadata returns metadata for a layer2 link
func Layer2Metadata() graph.Metadata {
	return graph.Metadata{"RelationType": Layer2Link}
}

// NamespaceFromNode returns the namespace name and the path of a node in the graph
func NamespaceFromNode(g *graph.Graph, n *graph.Node) (string, string, error) {
	name, _ := n.GetFieldString("Name")
	if name == "" {
		return "", "", fmt.Errorf("No Name for node %v", n)
	}

	nodes := g.LookupShortestPath(n, graph.Metadata{"Type": "host"}, OwnershipMetadata())
	if len(nodes) == 0 {
		return "", "", ErrNoPathToHost(name)
	}

	for _, node := range nodes {
		tp, _ := node.GetFieldString("Type")
		if tp == "" {
			return "", "", fmt.Errorf("No Type for node %v", n)
		}

		if tp == "netns" {
			name, _ := node.GetFieldString("Name")
			if name == "" {
				return "", "", fmt.Errorf("No Name for node %v", node)
			}

			path, _ := node.GetFieldString("Path")
			if path == "" {
				return "", "", fmt.Errorf("No Path for node %v", node)
			}

			return name, path, nil
		}
	}

	return "", "", nil
}

// NewNetNSContextByNode creates a new network namespace context based on the node
func NewNetNSContextByNode(g *graph.Graph, n *graph.Node) (*common.NetNSContext, error) {
	name, path, err := NamespaceFromNode(g, n)
	if err != nil || name == "" || path == "" {
		return nil, err
	}

	logging.GetLogger().Debugf("Switching to namespace %s (path: %s)", name, path)
	return common.NewNetNsContext(path)
}

// HostNodeTIDMap a map that store the value node TID and the key node host
type HostNodeTIDMap map[string][]string

// BuildHostNodeTIDMap creates a new node and host (key) map
func BuildHostNodeTIDMap(nodes []*graph.Node) HostNodeTIDMap {
	hnmap := make(HostNodeTIDMap)
	for _, node := range nodes {
		if tid, _ := node.GetFieldString("TID"); tid != "" {
			hnmap[node.Host] = append(hnmap[node.Host], tid)
		}
	}
	return hnmap
}

// HaveOwnershipLink returns true if parent and child have an ownership link
func HaveOwnershipLink(g *graph.Graph, parent *graph.Node, child *graph.Node) bool {
	return HaveLink(g, parent, child, OwnershipLink)
}

// IsOwnershipLinked checks whether the node has an OwnershipLink
func IsOwnershipLinked(g *graph.Graph, node *graph.Node) bool {
	edges := g.GetNodeEdges(node, OwnershipMetadata())
	return len(edges) != 0
}

// GetOwnershipLink get ownership Link between the parent and the child node or nil
func GetOwnershipLink(g *graph.Graph, parent *graph.Node, child *graph.Node) *graph.Edge {
	return g.GetFirstLink(parent, child, OwnershipMetadata())
}

// AddOwnershipLink Link between the parent and the child node, the child can have only one parent, previous will be overwritten
func AddOwnershipLink(g *graph.Graph, parent *graph.Node, child *graph.Node, metadata graph.Metadata) (*graph.Edge, error) {
	// a child node can only have one parent of type ownership, so delete the previous link
	for _, e := range g.GetNodeEdges(child, OwnershipMetadata()) {
		if e.Child == child.ID {
			logging.GetLogger().Debugf("Delete previous ownership link: %v", e)
			if err := g.DelEdge(e); err != nil {
				return nil, err
			}
		}
	}

	return AddLink(g, parent, child, OwnershipLink, metadata)
}

// HaveLink returns true if parent and child are linked
func HaveLink(g *graph.Graph, node1 *graph.Node, node2 *graph.Node, relationType string) bool {
	return g.AreLinked(node1, node2, graph.Metadata{"RelationType": relationType})
}

// AddLink minks the parent and the child node with the specified relation type and metadata
func AddLink(g *graph.Graph, node1 *graph.Node, node2 *graph.Node, relationType string, metadata graph.Metadata) (*graph.Edge, error) {
	m := graph.Metadata{"RelationType": relationType}
	for k, v := range metadata {
		m[k] = v
	}

	id, _ := uuid.NewV5(uuid.NamespaceOID, []byte(node1.ID+node2.ID+graph.Identifier(relationType)))
	return g.NewEdge(graph.Identifier(id.String()), node1, node2, m)
}

// HaveLayer2Link returns true if parent and child have the same layer 2
func HaveLayer2Link(g *graph.Graph, node1 *graph.Node, node2 *graph.Node) bool {
	return HaveLink(g, node1, node2, Layer2Link)
}

// AddLayer2Link links the parent and the child node
func AddLayer2Link(g *graph.Graph, node1 *graph.Node, node2 *graph.Node, metadata graph.Metadata) (*graph.Edge, error) {
	return AddLink(g, node1, node2, Layer2Link, metadata)
}

// IsInterfaceUp returns whether an interface has the flag UP set
func IsInterfaceUp(node *graph.Node) bool {
	linkFlags, _ := node.GetFieldStringList("LinkFlags")
	for _, flag := range linkFlags {
		if flag == "UP" {
			return true
		}
	}
	return false
}
<commit_msg>topology: add NewLink method<commit_after>/*
 * Copyright (C) 2016 Red Hat, Inc.
 *
 * 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 ofthe 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 specificlanguage governing permissions and
 * limitations under the License.
 *
 */

package topology

import (
	"fmt"
	"time"

	uuid "github.com/nu7hatch/gouuid"
	"github.com/skydive-project/skydive/common"
	"github.com/skydive-project/skydive/graffiti/graph"
	"github.com/skydive-project/skydive/logging"
)

// ErrNetworkPathNotFound
const (
	OwnershipLink = "ownership"
	Layer2Link    = "layer2"
)

var (
	// ErrNoPathToHost is called when no host could be found as the parent of a node
	ErrNoPathToHost = func(name string) error { return fmt.Errorf("Failed to determine network namespace path for %s", name) }
)

// OwnershipMetadata returns metadata for an ownership link
func OwnershipMetadata() graph.Metadata {
	return graph.Metadata{"RelationType": OwnershipLink}
}

// Layer2Metadata returns metadata for a layer2 link
func Layer2Metadata() graph.Metadata {
	return graph.Metadata{"RelationType": Layer2Link}
}

// NamespaceFromNode returns the namespace name and the path of a node in the graph
func NamespaceFromNode(g *graph.Graph, n *graph.Node) (string, string, error) {
	name, _ := n.GetFieldString("Name")
	if name == "" {
		return "", "", fmt.Errorf("No Name for node %v", n)
	}

	nodes := g.LookupShortestPath(n, graph.Metadata{"Type": "host"}, OwnershipMetadata())
	if len(nodes) == 0 {
		return "", "", ErrNoPathToHost(name)
	}

	for _, node := range nodes {
		tp, _ := node.GetFieldString("Type")
		if tp == "" {
			return "", "", fmt.Errorf("No Type for node %v", n)
		}

		if tp == "netns" {
			name, _ := node.GetFieldString("Name")
			if name == "" {
				return "", "", fmt.Errorf("No Name for node %v", node)
			}

			path, _ := node.GetFieldString("Path")
			if path == "" {
				return "", "", fmt.Errorf("No Path for node %v", node)
			}

			return name, path, nil
		}
	}

	return "", "", nil
}

// NewNetNSContextByNode creates a new network namespace context based on the node
func NewNetNSContextByNode(g *graph.Graph, n *graph.Node) (*common.NetNSContext, error) {
	name, path, err := NamespaceFromNode(g, n)
	if err != nil || name == "" || path == "" {
		return nil, err
	}

	logging.GetLogger().Debugf("Switching to namespace %s (path: %s)", name, path)
	return common.NewNetNsContext(path)
}

// HostNodeTIDMap a map that store the value node TID and the key node host
type HostNodeTIDMap map[string][]string

// BuildHostNodeTIDMap creates a new node and host (key) map
func BuildHostNodeTIDMap(nodes []*graph.Node) HostNodeTIDMap {
	hnmap := make(HostNodeTIDMap)
	for _, node := range nodes {
		if tid, _ := node.GetFieldString("TID"); tid != "" {
			hnmap[node.Host] = append(hnmap[node.Host], tid)
		}
	}
	return hnmap
}

// HaveOwnershipLink returns true if parent and child have an ownership link
func HaveOwnershipLink(g *graph.Graph, parent *graph.Node, child *graph.Node) bool {
	return HaveLink(g, parent, child, OwnershipLink)
}

// IsOwnershipLinked checks whether the node has an OwnershipLink
func IsOwnershipLinked(g *graph.Graph, node *graph.Node) bool {
	edges := g.GetNodeEdges(node, OwnershipMetadata())
	return len(edges) != 0
}

// GetOwnershipLink get ownership Link between the parent and the child node or nil
func GetOwnershipLink(g *graph.Graph, parent *graph.Node, child *graph.Node) *graph.Edge {
	return g.GetFirstLink(parent, child, OwnershipMetadata())
}

// AddOwnershipLink Link between the parent and the child node, the child can have only one parent, previous will be overwritten
func AddOwnershipLink(g *graph.Graph, parent *graph.Node, child *graph.Node, metadata graph.Metadata) (*graph.Edge, error) {
	// a child node can only have one parent of type ownership, so delete the previous link
	for _, e := range g.GetNodeEdges(child, OwnershipMetadata()) {
		if e.Child == child.ID {
			logging.GetLogger().Debugf("Delete previous ownership link: %v", e)
			if err := g.DelEdge(e); err != nil {
				return nil, err
			}
		}
	}

	return AddLink(g, parent, child, OwnershipLink, metadata)
}

// HaveLink returns true if parent and child are linked
func HaveLink(g *graph.Graph, node1 *graph.Node, node2 *graph.Node, relationType string) bool {
	return g.AreLinked(node1, node2, graph.Metadata{"RelationType": relationType})
}

// NewLink creates a link between a parent and a child node with the specified relation type and metadata
func NewLink(g *graph.Graph, node1 *graph.Node, node2 *graph.Node, relationType string, metadata graph.Metadata) (*graph.Edge, error) {
	m := graph.Metadata{"RelationType": relationType}
	for k, v := range metadata {
		m[k] = v
	}

	id, _ := uuid.NewV5(uuid.NamespaceOID, []byte(node1.ID+node2.ID+graph.Identifier(relationType)))
	edge := g.CreateEdge(graph.Identifier(id.String()), node1, node2, m, graph.Time(time.Now()))
	if edge == nil {
		return nil, fmt.Errorf("Failed to create edge with id %s", id.String())
	}
	return edge, nil
}

// AddLink links the parent and the child node with the specified relation type and metadata
func AddLink(g *graph.Graph, node1 *graph.Node, node2 *graph.Node, relationType string, metadata graph.Metadata) (*graph.Edge, error) {
	edge, err := NewLink(g, node1, node2, relationType, metadata)
	if err != nil {
		return nil, err
	}
	return edge, g.AddEdge(edge)
}

// HaveLayer2Link returns true if parent and child have the same layer 2
func HaveLayer2Link(g *graph.Graph, node1 *graph.Node, node2 *graph.Node) bool {
	return HaveLink(g, node1, node2, Layer2Link)
}

// AddLayer2Link links the parent and the child node
func AddLayer2Link(g *graph.Graph, node1 *graph.Node, node2 *graph.Node, metadata graph.Metadata) (*graph.Edge, error) {
	return AddLink(g, node1, node2, Layer2Link, metadata)
}

// IsInterfaceUp returns whether an interface has the flag UP set
func IsInterfaceUp(node *graph.Node) bool {
	linkFlags, _ := node.GetFieldStringList("LinkFlags")
	for _, flag := range linkFlags {
		if flag == "UP" {
			return true
		}
	}
	return false
}
<|endoftext|>