<commit_before>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package ssh

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"os/exec"
	"strings"

	"github.com/juju/utils"
)

var opensshCommonOptions = []string{"-o", "StrictHostKeyChecking no"}

// default identities will not be attempted if
// -i is specified and they are not explcitly
// included.
var defaultIdentities = []string{
	"~/.ssh/identity",
	"~/.ssh/id_rsa",
	"~/.ssh/id_dsa",
	"~/.ssh/id_ecdsa",
}

type opensshCommandKind int

const (
	sshKind opensshCommandKind = iota
	scpKind
)

// sshpassWrap wraps the command/args with sshpass if it is found in $PATH
// and the SSHPASS environment variable is set. Otherwise, the original
// command/args are returned.
func sshpassWrap(cmd string, args []string) (string, []string) {
	if os.Getenv("SSHPASS") != "" {
		if path, err := exec.LookPath("sshpass"); err == nil {
			return path, append([]string{"-e", cmd}, args...)
		}
	}
	return cmd, args
}

// OpenSSHClient is an implementation of Client that
// uses the ssh and scp executables found in $PATH.
type OpenSSHClient struct{}

// NewOpenSSHClient creates a new OpenSSHClient.
// If the ssh and scp programs cannot be found
// in $PATH, then an error is returned.
func NewOpenSSHClient() (*OpenSSHClient, error) {
	var c OpenSSHClient
	if _, err := exec.LookPath("ssh"); err != nil {
		return nil, err
	}
	if _, err := exec.LookPath("scp"); err != nil {
		return nil, err
	}
	return &c, nil
}

func opensshOptions(options *Options, commandKind opensshCommandKind) []string {
	args := append([]string{}, opensshCommonOptions...)
	if options == nil {
		options = &Options{}
	}
	if len(options.proxyCommand) > 0 {
		args = append(args, "-o", "ProxyCommand "+utils.CommandString(options.proxyCommand...))
	}
	if !options.passwordAuthAllowed {
		args = append(args, "-o", "PasswordAuthentication no")
	}

	// We must set ServerAliveInterval or the server may
	// think we've become unresponsive on long running
	// command executions such as "apt-get upgrade".
	args = append(args, "-o", "ServerAliveInterval 30")

	if options.allocatePTY {
		args = append(args, "-t", "-t") // twice to force
	}
	if options.knownHostsFile != "" {
		args = append(args, "-o", "UserKnownHostsFile "+utils.CommandString(options.knownHostsFile))
	}
	identities := append([]string{}, options.identities...)
	if pk := PrivateKeyFiles(); len(pk) > 0 {
		// Add client keys as implicit identities
		identities = append(identities, pk...)
	}
	// If any identities are specified, the
	// default ones must be explicitly specified.
	if len(identities) > 0 {
		for _, identity := range defaultIdentities {
			path, err := utils.NormalizePath(identity)
			if err != nil {
				logger.Warningf("failed to normalize path %q: %v", identity, err)
				continue
			}
			if _, err := os.Stat(path); err == nil {
				identities = append(identities, path)
			}
		}
	}
	for _, identity := range identities {
		args = append(args, "-i", identity)
	}
	if options.port != 0 {
		port := fmt.Sprint(options.port)
		if commandKind == scpKind {
			// scp uses -P instead of -p (-p means preserve).
			args = append(args, "-P", port)
		} else {
			args = append(args, "-p", port)
		}
	}
	if options.verbosity > 0 {
		args = append(args, "-v")
	}
	return args
}

// Command implements Client.Command.
func (c *OpenSSHClient) Command(host string, command []string, options *Options) *Cmd {
	args := opensshOptions(options, sshKind)
	args = append(args, host)
	if len(command) > 0 {
		args = append(args, command...)
	}
	bin, args := sshpassWrap("ssh", args)
	logger.Tracef("running: %s %s", bin, utils.CommandString(args...))
	return &Cmd{impl: &opensshCmd{exec.Command(bin, args...)}}
}

// Copy implements Client.Copy.
func (c *OpenSSHClient) Copy(args []string, userOptions *Options) error {
	var options Options
	if userOptions != nil {
		options = *userOptions
		options.allocatePTY = false // doesn't make sense for scp
	}
	allArgs := opensshOptions(&options, scpKind)
	allArgs = append(allArgs, args...)
	bin, allArgs := sshpassWrap("scp", allArgs)
	cmd := exec.Command(bin, allArgs...)
	var stderr bytes.Buffer
	cmd.Stderr = &stderr
	logger.Tracef("running: %s %s", bin, utils.CommandString(args...))
	if err := cmd.Run(); err != nil {
		stderr := strings.TrimSpace(stderr.String())
		if len(stderr) > 0 {
			err = fmt.Errorf("%v (%v)", err, stderr)
		}
		return err
	}
	return nil
}

type opensshCmd struct {
	*exec.Cmd
}

func (c *opensshCmd) SetStdio(stdin io.Reader, stdout, stderr io.Writer) {
	c.Stdin, c.Stdout, c.Stderr = stdin, stdout, stderr
}

func (c *opensshCmd) StdinPipe() (io.WriteCloser, io.Reader, error) {
	wc, err := c.Cmd.StdinPipe()
	if err != nil {
		return nil, nil, err
	}
	return wc, c.Stdin, nil
}

func (c *opensshCmd) StdoutPipe() (io.ReadCloser, io.Writer, error) {
	rc, err := c.Cmd.StdoutPipe()
	if err != nil {
		return nil, nil, err
	}
	return rc, c.Stdout, nil
}

func (c *opensshCmd) StderrPipe() (io.ReadCloser, io.Writer, error) {
	rc, err := c.Cmd.StderrPipe()
	if err != nil {
		return nil, nil, err
	}
	return rc, c.Stderr, nil
}

func (c *opensshCmd) Kill() error {
	if c.Process == nil {
		return fmt.Errorf("process has not been started")
	}
	return c.Process.Kill()
}
<commit_msg>Use the IdentitiesOnly SSH option.<commit_after>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package ssh

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"os/exec"
	"strings"

	"github.com/juju/utils"
)

var opensshCommonOptions = []string{"-o", "StrictHostKeyChecking no"}

// default identities will not be attempted if
// -i is specified and they are not explcitly
// included.
var defaultIdentities = []string{
	"~/.ssh/identity",
	"~/.ssh/id_rsa",
	"~/.ssh/id_dsa",
	"~/.ssh/id_ecdsa",
}

type opensshCommandKind int

const (
	sshKind opensshCommandKind = iota
	scpKind
)

// sshpassWrap wraps the command/args with sshpass if it is found in $PATH
// and the SSHPASS environment variable is set. Otherwise, the original
// command/args are returned.
func sshpassWrap(cmd string, args []string) (string, []string) {
	if os.Getenv("SSHPASS") != "" {
		if path, err := exec.LookPath("sshpass"); err == nil {
			return path, append([]string{"-e", cmd}, args...)
		}
	}
	return cmd, args
}

// OpenSSHClient is an implementation of Client that
// uses the ssh and scp executables found in $PATH.
type OpenSSHClient struct{}

// NewOpenSSHClient creates a new OpenSSHClient.
// If the ssh and scp programs cannot be found
// in $PATH, then an error is returned.
func NewOpenSSHClient() (*OpenSSHClient, error) {
	var c OpenSSHClient
	if _, err := exec.LookPath("ssh"); err != nil {
		return nil, err
	}
	if _, err := exec.LookPath("scp"); err != nil {
		return nil, err
	}
	return &c, nil
}

func opensshOptions(options *Options, commandKind opensshCommandKind) []string {
	args := append([]string{}, opensshCommonOptions...)
	if options == nil {
		options = &Options{}
	}
	if len(options.proxyCommand) > 0 {
		args = append(args, "-o", "ProxyCommand "+utils.CommandString(options.proxyCommand...))
	}
	if !options.passwordAuthAllowed {
		args = append(args, "-o", "PasswordAuthentication no")
	}

	// We must set ServerAliveInterval or the server may
	// think we've become unresponsive on long running
	// command executions such as "apt-get upgrade".
	args = append(args, "-o", "ServerAliveInterval 30")

	if options.allocatePTY {
		args = append(args, "-t", "-t") // twice to force
	}
	if options.knownHostsFile != "" {
		args = append(args, "-o", "UserKnownHostsFile "+utils.CommandString(options.knownHostsFile))
	}
	identities := append([]string{}, options.identities...)
	if pk := PrivateKeyFiles(); len(pk) > 0 {
		// Add client keys as implicit identities
		identities = append(identities, pk...)
	}
	// If any identities are specified, the
	// default ones must be explicitly specified.
	if len(identities) > 0 {
		// Restrict SSH to only the explicitly provided identity files.
		// Otherwise we may run out of authentication attempts if the
		// user has many identity files.
		args = append(args, "-o", "IdentitiesOnly yes")
		for _, identity := range defaultIdentities {
			path, err := utils.NormalizePath(identity)
			if err != nil {
				logger.Warningf("failed to normalize path %q: %v", identity, err)
				continue
			}
			if _, err := os.Stat(path); err == nil {
				identities = append(identities, path)
			}
		}
	}
	for _, identity := range identities {
		args = append(args, "-i", identity)
	}
	if options.port != 0 {
		port := fmt.Sprint(options.port)
		if commandKind == scpKind {
			// scp uses -P instead of -p (-p means preserve).
			args = append(args, "-P", port)
		} else {
			args = append(args, "-p", port)
		}
	}
	if options.verbosity > 0 {
		args = append(args, "-v")
	}
	return args
}

// Command implements Client.Command.
func (c *OpenSSHClient) Command(host string, command []string, options *Options) *Cmd {
	args := opensshOptions(options, sshKind)
	args = append(args, host)
	if len(command) > 0 {
		args = append(args, command...)
	}
	bin, args := sshpassWrap("ssh", args)
	logger.Tracef("running: %s %s", bin, utils.CommandString(args...))
	return &Cmd{impl: &opensshCmd{exec.Command(bin, args...)}}
}

// Copy implements Client.Copy.
func (c *OpenSSHClient) Copy(args []string, userOptions *Options) error {
	var options Options
	if userOptions != nil {
		options = *userOptions
		options.allocatePTY = false // doesn't make sense for scp
	}
	allArgs := opensshOptions(&options, scpKind)
	allArgs = append(allArgs, args...)
	bin, allArgs := sshpassWrap("scp", allArgs)
	cmd := exec.Command(bin, allArgs...)
	var stderr bytes.Buffer
	cmd.Stderr = &stderr
	logger.Tracef("running: %s %s", bin, utils.CommandString(args...))
	if err := cmd.Run(); err != nil {
		stderr := strings.TrimSpace(stderr.String())
		if len(stderr) > 0 {
			err = fmt.Errorf("%v (%v)", err, stderr)
		}
		return err
	}
	return nil
}

type opensshCmd struct {
	*exec.Cmd
}

func (c *opensshCmd) SetStdio(stdin io.Reader, stdout, stderr io.Writer) {
	c.Stdin, c.Stdout, c.Stderr = stdin, stdout, stderr
}

func (c *opensshCmd) StdinPipe() (io.WriteCloser, io.Reader, error) {
	wc, err := c.Cmd.StdinPipe()
	if err != nil {
		return nil, nil, err
	}
	return wc, c.Stdin, nil
}

func (c *opensshCmd) StdoutPipe() (io.ReadCloser, io.Writer, error) {
	rc, err := c.Cmd.StdoutPipe()
	if err != nil {
		return nil, nil, err
	}
	return rc, c.Stdout, nil
}

func (c *opensshCmd) StderrPipe() (io.ReadCloser, io.Writer, error) {
	rc, err := c.Cmd.StderrPipe()
	if err != nil {
		return nil, nil, err
	}
	return rc, c.Stderr, nil
}

func (c *opensshCmd) Kill() error {
	if c.Process == nil {
		return fmt.Errorf("process has not been started")
	}
	return c.Process.Kill()
}
<|endoftext|>


<commit_before>package v3

import (
	"fmt"

	"github.com/cloudfoundry-incubator/cf-test-helpers/cf"
	"github.com/cloudfoundry-incubator/cf-test-helpers/generator"
	"github.com/cloudfoundry-incubator/cf-test-helpers/helpers"
	"github.com/cloudfoundry/cf-acceptance-tests/helpers/assets"
	. "github.com/cloudfoundry/cf-acceptance-tests/helpers/v3_helpers"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
)

var _ = Describe("v3 buildpack app lifecycle", func() {
	var (
		appName                         string
		appGuid                         string
		packageGuid                     string
		spaceGuid                       string
		appCreationEnvironmentVariables string
		token                           string
		uploadUrl                       string
	)

	BeforeEach(func() {
		appName = generator.PrefixedRandomName("CATS-APP-")
		spaceGuid = GetSpaceGuidFromName(context.RegularUserContext().Space)
		appCreationEnvironmentVariables = `"foo"=>"bar"`
		appGuid = CreateApp(appName, spaceGuid, `{"foo":"bar"}`)
		packageGuid = CreatePackage(appGuid)
		token = GetAuthToken()
		uploadUrl = fmt.Sprintf("%s%s/v3/packages/%s/upload", config.Protocol(), config.ApiEndpoint, packageGuid)
	})

	AfterEach(func() {
		FetchRecentLogs(appGuid, token, config)
		DeleteApp(appGuid)
	})

	Context("with a ruby_buildpack", func() {
		BeforeEach(func() {
			UploadPackage(uploadUrl, assets.NewAssets().DoraZip, token)
			WaitForPackageToBeReady(packageGuid)
		})

		It("can run apps with processes from the Procfile", func() {
			dropletGuid := StageBuildpackPackage(packageGuid, "ruby_buildpack")
			WaitForDropletToStage(dropletGuid)

			AssignDropletToApp(appGuid, dropletGuid)

			processes := GetProcesses(appGuid, appName)
			webProcess := GetProcessByType(processes, "web")
			workerProcess := GetProcessByType(processes, "worker")

			Expect(webProcess.Guid).ToNot(BeEmpty())
			Expect(workerProcess.Guid).ToNot(BeEmpty())

			CreateAndMapRoute(appGuid, context.RegularUserContext().Space, helpers.LoadConfig().AppsDomain, webProcess.Name)

			StartApp(appGuid)

			Eventually(func() string {
				return helpers.CurlAppRoot(webProcess.Name)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))

			output := helpers.CurlApp(webProcess.Name, "/env")
			Expect(output).To(ContainSubstring(fmt.Sprintf("application_name\\\":\\\"%s", appName)))
			Expect(output).To(ContainSubstring(appCreationEnvironmentVariables))

			Expect(cf.Cf("apps").Wait(DEFAULT_TIMEOUT)).To(Say(fmt.Sprintf("%s\\s+started", webProcess.Name)))
			Expect(cf.Cf("apps").Wait(DEFAULT_TIMEOUT)).To(Say(fmt.Sprintf("%s\\s+started", workerProcess.Name)))

			usageEvents := LastPageUsageEvents(context)

			event1 := AppUsageEvent{Entity{ProcessType: webProcess.Type, AppGuid: webProcess.Guid, State: "STARTED", ParentAppGuid: appGuid, ParentAppName: appName}}
			event2 := AppUsageEvent{Entity{ProcessType: workerProcess.Type, AppGuid: workerProcess.Guid, State: "STARTED", ParentAppGuid: appGuid, ParentAppName: appName}}
			Expect(UsageEventsInclude(usageEvents, event1)).To(BeTrue())
			Expect(UsageEventsInclude(usageEvents, event2)).To(BeTrue())

			StopApp(appGuid)

			Expect(cf.Cf("apps").Wait(DEFAULT_TIMEOUT)).To(Say(fmt.Sprintf("%s\\s+stopped", webProcess.Name)))
			Expect(cf.Cf("apps").Wait(DEFAULT_TIMEOUT)).To(Say(fmt.Sprintf("%s\\s+stopped", workerProcess.Name)))

			usageEvents = LastPageUsageEvents(context)
			event1 = AppUsageEvent{Entity{ProcessType: webProcess.Type, AppGuid: webProcess.Guid, State: "STOPPED", ParentAppGuid: appGuid, ParentAppName: appName}}
			event2 = AppUsageEvent{Entity{ProcessType: workerProcess.Type, AppGuid: workerProcess.Guid, State: "STOPPED", ParentAppGuid: appGuid, ParentAppName: appName}}
			Expect(UsageEventsInclude(usageEvents, event1)).To(BeTrue())
			Expect(UsageEventsInclude(usageEvents, event2)).To(BeTrue())

			Eventually(func() string {
				return helpers.CurlAppRoot(webProcess.Name)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("404"))
		})
	})

	Context("with a java_buildpack", func() {
		BeforeEach(func() {
			UploadPackage(uploadUrl, assets.NewAssets().JavaSpringZip, token)
			WaitForPackageToBeReady(packageGuid)
		})

		It("can run spring apps", func() {
			dropletGuid := StageBuildpackPackage(packageGuid, "java_buildpack")
			WaitForDropletToStage(dropletGuid)

			AssignDropletToApp(appGuid, dropletGuid)

			processes := GetProcesses(appGuid, appName)
			webProcess := GetProcessByType(processes, "web")

			Expect(webProcess.Guid).ToNot(BeEmpty())

			CreateAndMapRoute(appGuid, context.RegularUserContext().Space, helpers.LoadConfig().AppsDomain, webProcess.Name)

			StartApp(appGuid)

			Eventually(func() string {
				return helpers.CurlAppRoot(webProcess.Name)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("ok"))

			Expect(cf.Cf("apps").Wait(DEFAULT_TIMEOUT)).To(Say(fmt.Sprintf("%s\\s+started", webProcess.Name)))

			usageEvents := LastPageUsageEvents(context)

			event1 := AppUsageEvent{Entity{ProcessType: webProcess.Type, AppGuid: webProcess.Guid, State: "STARTED", ParentAppGuid: appGuid, ParentAppName: appName}}
			Expect(UsageEventsInclude(usageEvents, event1)).To(BeTrue())

			StopApp(appGuid)

			Expect(cf.Cf("apps").Wait(DEFAULT_TIMEOUT)).To(Say(fmt.Sprintf("%s\\s+stopped", webProcess.Name)))

			usageEvents = LastPageUsageEvents(context)
			event1 = AppUsageEvent{Entity{ProcessType: webProcess.Type, AppGuid: webProcess.Guid, State: "STOPPED", ParentAppGuid: appGuid, ParentAppName: appName}}
			Expect(UsageEventsInclude(usageEvents, event1)).To(BeTrue())

			Eventually(func() string {
				return helpers.CurlAppRoot(webProcess.Name)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("404"))
		})
	})
})

var _ = Describe("v3 docker app lifecycle", func() {
	config := helpers.LoadConfig()
	if config.IncludeDiegoDocker {
		var (
			appName                         string
			appGuid                         string
			packageGuid                     string
			spaceGuid                       string
			appCreationEnvironmentVariables string
			token                           string
		)

		BeforeEach(func() {
			appName = generator.PrefixedRandomName("CATS-APP-")
			spaceGuid = GetSpaceGuidFromName(context.RegularUserContext().Space)
			appCreationEnvironmentVariables = `"foo":"bar"`
			appGuid = CreateDockerApp(appName, spaceGuid, `{"foo":"bar"}`)
			packageGuid = CreateDockerPackage(appGuid, "cloudfoundry/diego-docker-app:latest")
			token = GetAuthToken()
		})

		AfterEach(func() {
			FetchRecentLogs(appGuid, token, config)
			DeleteApp(appGuid)
		})

		It("can run apps", func() {
			dropletGuid := StageDockerPackage(packageGuid)
			WaitForDropletToStage(dropletGuid)

			AssignDropletToApp(appGuid, dropletGuid)

			processes := GetProcesses(appGuid, appName)
			webProcess := GetProcessByType(processes, "web")

			Expect(webProcess.Guid).ToNot(BeEmpty())

			CreateAndMapRoute(appGuid, context.RegularUserContext().Space, helpers.LoadConfig().AppsDomain, webProcess.Name)

			StartApp(appGuid)

			Eventually(func() string {
				return helpers.CurlAppRoot(webProcess.Name)
			}, DEFAULT_TIMEOUT).Should(Equal("0"))

			output := helpers.CurlApp(webProcess.Name, "/env")
			Expect(output).To(ContainSubstring(fmt.Sprintf("application_name\\\":\\\"%s", appName)))
			Expect(output).To(ContainSubstring(appCreationEnvironmentVariables))

			Expect(cf.Cf("apps").Wait(DEFAULT_TIMEOUT)).To(Say(fmt.Sprintf("%s\\s+started", webProcess.Name)))

			usageEvents := LastPageUsageEvents(context)

			event := AppUsageEvent{Entity{ProcessType: webProcess.Type, AppGuid: webProcess.Guid, State: "STARTED", ParentAppGuid: appGuid, ParentAppName: appName}}
			Expect(UsageEventsInclude(usageEvents, event)).To(BeTrue())

			StopApp(appGuid)

			Expect(cf.Cf("apps").Wait(DEFAULT_TIMEOUT)).To(Say(fmt.Sprintf("%s\\s+stopped", webProcess.Name)))

			usageEvents = LastPageUsageEvents(context)
			event = AppUsageEvent{Entity{ProcessType: webProcess.Type, AppGuid: webProcess.Guid, State: "STOPPED", ParentAppGuid: appGuid, ParentAppName: appName}}
			Expect(UsageEventsInclude(usageEvents, event)).To(BeTrue())

			Eventually(func() string {
				return helpers.CurlAppRoot(webProcess.Name)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("404"))
		})
	}
})
<commit_msg>Increase timeout when starting v3 app<commit_after>package v3

import (
	"fmt"

	"github.com/cloudfoundry-incubator/cf-test-helpers/cf"
	"github.com/cloudfoundry-incubator/cf-test-helpers/generator"
	"github.com/cloudfoundry-incubator/cf-test-helpers/helpers"
	"github.com/cloudfoundry/cf-acceptance-tests/helpers/assets"
	. "github.com/cloudfoundry/cf-acceptance-tests/helpers/v3_helpers"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
)

var _ = Describe("v3 buildpack app lifecycle", func() {
	var (
		appName                         string
		appGuid                         string
		packageGuid                     string
		spaceGuid                       string
		appCreationEnvironmentVariables string
		token                           string
		uploadUrl                       string
	)

	BeforeEach(func() {
		appName = generator.PrefixedRandomName("CATS-APP-")
		spaceGuid = GetSpaceGuidFromName(context.RegularUserContext().Space)
		appCreationEnvironmentVariables = `"foo"=>"bar"`
		appGuid = CreateApp(appName, spaceGuid, `{"foo":"bar"}`)
		packageGuid = CreatePackage(appGuid)
		token = GetAuthToken()
		uploadUrl = fmt.Sprintf("%s%s/v3/packages/%s/upload", config.Protocol(), config.ApiEndpoint, packageGuid)
	})

	AfterEach(func() {
		FetchRecentLogs(appGuid, token, config)
		DeleteApp(appGuid)
	})

	Context("with a ruby_buildpack", func() {
		BeforeEach(func() {
			UploadPackage(uploadUrl, assets.NewAssets().DoraZip, token)
			WaitForPackageToBeReady(packageGuid)
		})

		It("can run apps with processes from the Procfile", func() {
			dropletGuid := StageBuildpackPackage(packageGuid, "ruby_buildpack")
			WaitForDropletToStage(dropletGuid)

			AssignDropletToApp(appGuid, dropletGuid)

			processes := GetProcesses(appGuid, appName)
			webProcess := GetProcessByType(processes, "web")
			workerProcess := GetProcessByType(processes, "worker")

			Expect(webProcess.Guid).ToNot(BeEmpty())
			Expect(workerProcess.Guid).ToNot(BeEmpty())

			CreateAndMapRoute(appGuid, context.RegularUserContext().Space, helpers.LoadConfig().AppsDomain, webProcess.Name)

			StartApp(appGuid)

			Eventually(func() string {
				return helpers.CurlAppRoot(webProcess.Name)
			}, CF_PUSH_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))

			output := helpers.CurlApp(webProcess.Name, "/env")
			Expect(output).To(ContainSubstring(fmt.Sprintf("application_name\\\":\\\"%s", appName)))
			Expect(output).To(ContainSubstring(appCreationEnvironmentVariables))

			Expect(cf.Cf("apps").Wait(DEFAULT_TIMEOUT)).To(Say(fmt.Sprintf("%s\\s+started", webProcess.Name)))
			Expect(cf.Cf("apps").Wait(DEFAULT_TIMEOUT)).To(Say(fmt.Sprintf("%s\\s+started", workerProcess.Name)))

			usageEvents := LastPageUsageEvents(context)

			event1 := AppUsageEvent{Entity{ProcessType: webProcess.Type, AppGuid: webProcess.Guid, State: "STARTED", ParentAppGuid: appGuid, ParentAppName: appName}}
			event2 := AppUsageEvent{Entity{ProcessType: workerProcess.Type, AppGuid: workerProcess.Guid, State: "STARTED", ParentAppGuid: appGuid, ParentAppName: appName}}
			Expect(UsageEventsInclude(usageEvents, event1)).To(BeTrue())
			Expect(UsageEventsInclude(usageEvents, event2)).To(BeTrue())

			StopApp(appGuid)

			Expect(cf.Cf("apps").Wait(DEFAULT_TIMEOUT)).To(Say(fmt.Sprintf("%s\\s+stopped", webProcess.Name)))
			Expect(cf.Cf("apps").Wait(DEFAULT_TIMEOUT)).To(Say(fmt.Sprintf("%s\\s+stopped", workerProcess.Name)))

			usageEvents = LastPageUsageEvents(context)
			event1 = AppUsageEvent{Entity{ProcessType: webProcess.Type, AppGuid: webProcess.Guid, State: "STOPPED", ParentAppGuid: appGuid, ParentAppName: appName}}
			event2 = AppUsageEvent{Entity{ProcessType: workerProcess.Type, AppGuid: workerProcess.Guid, State: "STOPPED", ParentAppGuid: appGuid, ParentAppName: appName}}
			Expect(UsageEventsInclude(usageEvents, event1)).To(BeTrue())
			Expect(UsageEventsInclude(usageEvents, event2)).To(BeTrue())

			Eventually(func() string {
				return helpers.CurlAppRoot(webProcess.Name)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("404"))
		})
	})

	Context("with a java_buildpack", func() {
		BeforeEach(func() {
			UploadPackage(uploadUrl, assets.NewAssets().JavaSpringZip, token)
			WaitForPackageToBeReady(packageGuid)
		})

		It("can run spring apps", func() {
			dropletGuid := StageBuildpackPackage(packageGuid, "java_buildpack")
			WaitForDropletToStage(dropletGuid)

			AssignDropletToApp(appGuid, dropletGuid)

			processes := GetProcesses(appGuid, appName)
			webProcess := GetProcessByType(processes, "web")

			Expect(webProcess.Guid).ToNot(BeEmpty())

			CreateAndMapRoute(appGuid, context.RegularUserContext().Space, helpers.LoadConfig().AppsDomain, webProcess.Name)

			StartApp(appGuid)

			Eventually(func() string {
				return helpers.CurlAppRoot(webProcess.Name)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("ok"))

			Expect(cf.Cf("apps").Wait(DEFAULT_TIMEOUT)).To(Say(fmt.Sprintf("%s\\s+started", webProcess.Name)))

			usageEvents := LastPageUsageEvents(context)

			event1 := AppUsageEvent{Entity{ProcessType: webProcess.Type, AppGuid: webProcess.Guid, State: "STARTED", ParentAppGuid: appGuid, ParentAppName: appName}}
			Expect(UsageEventsInclude(usageEvents, event1)).To(BeTrue())

			StopApp(appGuid)

			Expect(cf.Cf("apps").Wait(DEFAULT_TIMEOUT)).To(Say(fmt.Sprintf("%s\\s+stopped", webProcess.Name)))

			usageEvents = LastPageUsageEvents(context)
			event1 = AppUsageEvent{Entity{ProcessType: webProcess.Type, AppGuid: webProcess.Guid, State: "STOPPED", ParentAppGuid: appGuid, ParentAppName: appName}}
			Expect(UsageEventsInclude(usageEvents, event1)).To(BeTrue())

			Eventually(func() string {
				return helpers.CurlAppRoot(webProcess.Name)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("404"))
		})
	})
})

var _ = Describe("v3 docker app lifecycle", func() {
	config := helpers.LoadConfig()
	if config.IncludeDiegoDocker {
		var (
			appName                         string
			appGuid                         string
			packageGuid                     string
			spaceGuid                       string
			appCreationEnvironmentVariables string
			token                           string
		)

		BeforeEach(func() {
			appName = generator.PrefixedRandomName("CATS-APP-")
			spaceGuid = GetSpaceGuidFromName(context.RegularUserContext().Space)
			appCreationEnvironmentVariables = `"foo":"bar"`
			appGuid = CreateDockerApp(appName, spaceGuid, `{"foo":"bar"}`)
			packageGuid = CreateDockerPackage(appGuid, "cloudfoundry/diego-docker-app:latest")
			token = GetAuthToken()
		})

		AfterEach(func() {
			FetchRecentLogs(appGuid, token, config)
			DeleteApp(appGuid)
		})

		It("can run apps", func() {
			dropletGuid := StageDockerPackage(packageGuid)
			WaitForDropletToStage(dropletGuid)

			AssignDropletToApp(appGuid, dropletGuid)

			processes := GetProcesses(appGuid, appName)
			webProcess := GetProcessByType(processes, "web")

			Expect(webProcess.Guid).ToNot(BeEmpty())

			CreateAndMapRoute(appGuid, context.RegularUserContext().Space, helpers.LoadConfig().AppsDomain, webProcess.Name)

			StartApp(appGuid)

			Eventually(func() string {
				return helpers.CurlAppRoot(webProcess.Name)
			}, DEFAULT_TIMEOUT).Should(Equal("0"))

			output := helpers.CurlApp(webProcess.Name, "/env")
			Expect(output).To(ContainSubstring(fmt.Sprintf("application_name\\\":\\\"%s", appName)))
			Expect(output).To(ContainSubstring(appCreationEnvironmentVariables))

			Expect(cf.Cf("apps").Wait(DEFAULT_TIMEOUT)).To(Say(fmt.Sprintf("%s\\s+started", webProcess.Name)))

			usageEvents := LastPageUsageEvents(context)

			event := AppUsageEvent{Entity{ProcessType: webProcess.Type, AppGuid: webProcess.Guid, State: "STARTED", ParentAppGuid: appGuid, ParentAppName: appName}}
			Expect(UsageEventsInclude(usageEvents, event)).To(BeTrue())

			StopApp(appGuid)

			Expect(cf.Cf("apps").Wait(DEFAULT_TIMEOUT)).To(Say(fmt.Sprintf("%s\\s+stopped", webProcess.Name)))

			usageEvents = LastPageUsageEvents(context)
			event = AppUsageEvent{Entity{ProcessType: webProcess.Type, AppGuid: webProcess.Guid, State: "STOPPED", ParentAppGuid: appGuid, ParentAppName: appName}}
			Expect(UsageEventsInclude(usageEvents, event)).To(BeTrue())

			Eventually(func() string {
				return helpers.CurlAppRoot(webProcess.Name)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("404"))
		})
	}
})
<|endoftext|>


<commit_before>/*
Copyright 2018 Google LLC

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

    https://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.
*/

/*
Pyrios is an Elasticsearch proxy.
Pyrios forwards the HTTP request from a client to the on prem ElasticSearch cluster
and sends the response back to the client.
The in-cluster application can access the pyrios API endpoint via the `pyrios` service.
*/
package main

import (
	"cloud.google.com/go/compute/metadata"
	"context"
	"fmt"
	"go.opencensus.io/exporter/stackdriver"
	"go.opencensus.io/trace"
	"io"
	"log"
	"net/http"
	"os"
)

// Sets the default server IP and port for ElasticSearch server
const (
	defaultESServer = "127.0.0.1"
	defaultESPort   = 9200
)

/*
HandleHTTP is our main HTTP handler.
It takes the IP address of the on prem elasticsearch endpoint
in our demo. This is the regional static IP address exposed by the internal
load balancer of the on premise cluster's elastic-client service.
It takes a request from a client, forwards over to the on prem  elasticsearch
endpoint, then copy the response back.
*/
func handleHTTP(esServer string, w http.ResponseWriter, r *http.Request) {
	ep := *r.URL
	path := ep.Path
	ep.Host = fmt.Sprintf("%s:%d", esServer, defaultESPort)
	ep.Scheme = "http"

	startOptions := trace.WithSampler(trace.AlwaysSample())
	_, span := trace.StartSpan(r.Context(), path, startOptions)
	defer span.End()

	// Rewrite the request with the elasticsearch endpoint.
	req, err := http.NewRequest(r.Method, ep.String(), r.Body)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		log.Printf("error creating new request encountered: %v ", err)
		return
	}
	// Enforce the Content-Type for working with ES 6.x and up.
	// https://www.elastic.co/blog/strict-content-type-checking-for-elasticsearch-rest-requests
	req.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		log.Printf("error sending request to the on prem elasticsearch cluster encountered: %v ", err)
		return
	}

	// Copy the response head and body from the elasticsearch API 's response
	// and send back to the client side.
	w.WriteHeader(http.StatusOK)
	copyHeader(w.Header(), resp.Header)
	io.Copy(w, resp.Body)
	defer resp.Body.Close()
	log.Print("Pyrios proxy request handled successfully")
}

// Helper func copies http headers from src to dst.
// It returns nothing, dst is changed and returned as a copy of src.
func copyHeader(dst, src http.Header) {
	for k, vv := range src {
		for _, v := range vv {
			dst.Add(k, v)
		}
	}
}

// Our main entry function to read relevant environment variables
// and set up the http handler.
func main() {
	gcpProject := os.Getenv("GCP_PROJECT")
	var err error
	if gcpProject == "" {
		metadataClient := metadata.NewClient(http.DefaultClient)
		if metadataClient == nil {
			log.Fatal("Metadata client failed to create")
		}
		gcpProject, err = metadataClient.ProjectID()
		if err != nil {
			log.Fatal("Cannot determine GCP project")
		}
	}

	// Here we decide where to export our OpenCensus data.
	exporter, err := stackdriver.NewExporter(
		stackdriver.Options{
			ProjectID: gcpProject,
		})
	if err != nil {
		log.Fatal("Failed to create Stackdriver trace exporter")
	}
	trace.RegisterExporter(exporter)

	// Here we read ES_SERVER from the environment or use a default value.
	esServer := os.Getenv("ES_SERVER")
	if esServer == "" {
		log.Print(fmt.Sprintf("ES_SERVER is not set, using default value of %s", defaultESServer))
		esServer = defaultESServer
	}

	// Set up the HTTP server with a http handler.
	// https://godoc.org/net/http Custom Server.
	s := &http.Server{
		Addr: fmt.Sprintf(":%d", defaultESPort),
		Handler: http.HandlerFunc(
			func(w http.ResponseWriter, r *http.Request) {
				handleHTTP(esServer, w, r)
			})}

	defer s.Shutdown(context.Background())
	log.Print("Starting pyrios proxy")
	s.ListenAndServe()
}
<commit_msg>SD exporter was moved to contrib.go.opencensus.io: https://github.com/census-instrumentation/opencensus-go/pull/856<commit_after>/*
Copyright 2018 Google LLC

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

    https://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.
*/

/*
Pyrios is an Elasticsearch proxy.
Pyrios forwards the HTTP request from a client to the on prem ElasticSearch cluster
and sends the response back to the client.
The in-cluster application can access the pyrios API endpoint via the `pyrios` service.
*/
package main

import (
	"cloud.google.com/go/compute/metadata"
	"context"
	"fmt"
	// "go.opencensus.io/exporter/stackdriver"
	"contrib.go.opencensus.io/exporter/stackdriver"
	"go.opencensus.io/trace"
	"io"
	"log"
	"net/http"
	"os"
)

// Sets the default server IP and port for ElasticSearch server
const (
	defaultESServer = "127.0.0.1"
	defaultESPort   = 9200
)

/*
HandleHTTP is our main HTTP handler.
It takes the IP address of the on prem elasticsearch endpoint
in our demo. This is the regional static IP address exposed by the internal
load balancer of the on premise cluster's elastic-client service.
It takes a request from a client, forwards over to the on prem  elasticsearch
endpoint, then copy the response back.
*/
func handleHTTP(esServer string, w http.ResponseWriter, r *http.Request) {
	ep := *r.URL
	path := ep.Path
	ep.Host = fmt.Sprintf("%s:%d", esServer, defaultESPort)
	ep.Scheme = "http"

	startOptions := trace.WithSampler(trace.AlwaysSample())
	_, span := trace.StartSpan(r.Context(), path, startOptions)
	defer span.End()

	// Rewrite the request with the elasticsearch endpoint.
	req, err := http.NewRequest(r.Method, ep.String(), r.Body)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		log.Printf("error creating new request encountered: %v ", err)
		return
	}
	// Enforce the Content-Type for working with ES 6.x and up.
	// https://www.elastic.co/blog/strict-content-type-checking-for-elasticsearch-rest-requests
	req.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		log.Printf("error sending request to the on prem elasticsearch cluster encountered: %v ", err)
		return
	}

	// Copy the response head and body from the elasticsearch API 's response
	// and send back to the client side.
	w.WriteHeader(http.StatusOK)
	copyHeader(w.Header(), resp.Header)
	io.Copy(w, resp.Body)
	defer resp.Body.Close()
	log.Print("Pyrios proxy request handled successfully")
}

// Helper func copies http headers from src to dst.
// It returns nothing, dst is changed and returned as a copy of src.
func copyHeader(dst, src http.Header) {
	for k, vv := range src {
		for _, v := range vv {
			dst.Add(k, v)
		}
	}
}

// Our main entry function to read relevant environment variables
// and set up the http handler.
func main() {
	gcpProject := os.Getenv("GCP_PROJECT")
	var err error
	if gcpProject == "" {
		metadataClient := metadata.NewClient(http.DefaultClient)
		if metadataClient == nil {
			log.Fatal("Metadata client failed to create")
		}
		gcpProject, err = metadataClient.ProjectID()
		if err != nil {
			log.Fatal("Cannot determine GCP project")
		}
	}

	// Here we decide where to export our OpenCensus data.
	exporter, err := stackdriver.NewExporter(
		stackdriver.Options{
			ProjectID: gcpProject,
		})
	if err != nil {
		log.Fatal("Failed to create Stackdriver trace exporter")
	}
	trace.RegisterExporter(exporter)

	// Here we read ES_SERVER from the environment or use a default value.
	esServer := os.Getenv("ES_SERVER")
	if esServer == "" {
		log.Print(fmt.Sprintf("ES_SERVER is not set, using default value of %s", defaultESServer))
		esServer = defaultESServer
	}

	// Set up the HTTP server with a http handler.
	// https://godoc.org/net/http Custom Server.
	s := &http.Server{
		Addr: fmt.Sprintf(":%d", defaultESPort),
		Handler: http.HandlerFunc(
			func(w http.ResponseWriter, r *http.Request) {
				handleHTTP(esServer, w, r)
			})}

	defer s.Shutdown(context.Background())
	log.Print("Starting pyrios proxy")
	s.ListenAndServe()
}
<|endoftext|>


<commit_before>// Copyright 2016 Google Inc. All rights reserved.
//
// 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 query

import (
	"bytes"
	"fmt"
	"log"
	"regexp/syntax"
)

var _ = log.Printf

const ngramSize = 3

type SuggestQueryError struct {
	Message    string
	Suggestion string
}

func (e *SuggestQueryError) Error() string {
	return fmt.Sprintf("%s. Suggestion: %s", e.Message, e.Suggestion)
}

// parseStringLiteral parses a string literal, consumes the starting
// quote too.
func parseStringLiteral(in []byte) (lit []byte, n int, err error) {
	left := in[1:]
	found := false

loop:
	for len(left) > 0 {
		c := left[0]
		left = left[1:]
		switch c {
		case '"':
			found = true
			break loop
		case '\\':
			// TODO - other escape sequences.
			if len(left) == 0 {
				return nil, 0, fmt.Errorf("missing char after \\")
			}
			c = left[0]
			left = left[1:]

			lit = append(lit, c)
		default:
			lit = append(lit, c)
		}
	}
	if !found {
		return nil, 0, fmt.Errorf("unterminated quoted string")
	}
	return lit, len(in) - len(left), nil
}

type setCase string

// orOperator is a placeholder intermediate so we can represent [A,
// or, B] before we convert it to Or{A, B}
type orOperator struct{}

func (o *orOperator) String() string {
	return "orOp"
}

func isSpace(c byte) bool {
	return c == ' ' || c == '\t'
}

// Consumes KEYWORD:<arg>, where arg may be quoted.  Returns the
// keyword, the number of bytes consumed, whether it matched, and if
// there was an error.
func consumeKeyword(in []byte, kw []byte) ([]byte, int, bool, error) {
	if !bytes.HasPrefix(in, kw) {
		return nil, 0, false, nil
	}

	var arg []byte
	var err error
	left := in
	left = left[len(kw):]
done:
	for len(left) > 0 {
		c := left[0]
		switch {
		case c == '"':
			var n int
			arg, n, err = parseStringLiteral(left)
			if err != nil {
				return nil, 0, true, err
			}

			left = left[n:]
		case isSpace(c):
			break done
		default:
			arg = append(arg, c)
			left = left[1:]
		}
	}

	return arg, len(in) - len(left), true, nil
}

// Parse parses a string into a query.
func Parse(qStr string) (Q, error) {
	b := []byte(qStr)

	qs, _, err := parseExprList(b)
	if err != nil {
		return nil, err
	}

	q, err := parseOperators(qs)
	if err != nil {
		return nil, err
	}

	return Simplify(q), nil
}

// parseExpr parses a single expression, returning the result, and the
// number of bytes consumed.
func parseExpr(in []byte) (Q, int, error) {
	b := in[:]
	var expr Q
	for len(b) > 0 && isSpace(b[0]) {
		b = b[1:]
	}

	tok, err := nextToken(b)
	if err != nil {
		return nil, 0, err
	}
	if tok == nil {
		return nil, 0, nil
	}
	b = b[len(tok.Input):]

	text := string(tok.Text)
	switch tok.Type {
	case tokCase:
		switch text {
		case "yes":
		case "no":
		case "auto":
		default:
			return nil, 0, fmt.Errorf("unknown case argument %q, want {yes,no,auto}", text)
		}
		expr = &caseQ{text}
	case tokRepo:
		expr = &Repo{Pattern: text}
	case tokBranch:
		expr = &Branch{Pattern: text}
	case tokText, tokRegex:
		q, err := regexpQuery(text, false)
		if err != nil {
			return nil, 0, err
		}
		expr = q
	case tokFile:
		q, err := regexpQuery(text, true)
		if err != nil {
			return nil, 0, err
		}
		expr = q

	case tokParenClose:
		// Caller must consume paren.
		expr = nil

	case tokParenOpen:
		qs, n, err := parseExprList(b)
		b = b[n:]
		if err != nil {
			return nil, 0, err
		}

		pTok, err := nextToken(b)
		if err != nil {
			return nil, 0, err
		}
		if pTok == nil || pTok.Type != tokParenClose {
			return nil, 0, fmt.Errorf("missing close paren, got token %v", pTok)
		}

		b = b[len(pTok.Input):]
		expr, err = parseOperators(qs)
		if err != nil {
			return nil, 0, err
		}
	case tokNegate:
		subQ, n, err := parseExpr(b)
		if err != nil {
			return nil, 0, err
		}
		b = b[n:]
		expr = &Not{subQ}
	}

	return expr, len(in) - len(b), nil
}

// regexpQuery parses an atom into either a regular expression, or a
// simple substring atom.
func regexpQuery(text string, file bool) (Q, error) {
	var expr Q

	r, err := syntax.Parse(text, syntax.Perl)
	if err != nil {
		return nil, err
	}

	if r.Op == syntax.OpLiteral {
		expr = &Substring{
			Pattern:  string(r.Rune),
			FileName: file,
		}
	} else {
		expr = &Regexp{
			Regexp:   r,
			FileName: file,
		}
	}

	return expr, nil
}

// parseOperators interprets the orOperator in a list of queries.
func parseOperators(in []Q) (Q, error) {
	top := &Or{}
	cur := &And{}

	seenOr := false
	for _, q := range in {
		if _, ok := q.(*orOperator); ok {
			seenOr = true
			if len(cur.Children) == 0 {
				return nil, fmt.Errorf("OR operator should have operand")
			}
			top.Children = append(top.Children, cur)
			cur = &And{}
		} else {
			cur.Children = append(cur.Children, q)
		}
	}

	if seenOr && len(cur.Children) == 0 {
		return nil, fmt.Errorf("OR operator should have operand")
	}
	top.Children = append(top.Children, cur)
	return top, nil
}

// parseExprList parses a list of query expressions. It is the
// workhorse of the Parse function.
func parseExprList(in []byte) ([]Q, int, error) {
	b := in[:]
	var qs []Q
	for len(b) > 0 {
		for len(b) > 0 && isSpace(b[0]) {
			b = b[1:]
		}
		tok, _ := nextToken(b)
		if tok != nil && tok.Type == tokParenClose {
			break
		} else if tok != nil && tok.Type == tokOr {
			qs = append(qs, &orOperator{})
			b = b[len(tok.Input):]
			continue
		}

		q, n, err := parseExpr(b)
		if err != nil {
			return nil, 0, err
		}

		if q == nil {
			// eof or a ')'
			break
		}
		qs = append(qs, q)
		b = b[n:]
	}

	setCase := "auto"
	newQS := qs[:0]
	for _, q := range qs {
		if sc, ok := q.(*caseQ); ok {
			setCase = sc.Flavor
		} else {
			newQS = append(newQS, q)
		}
	}
	qs = newQS
	for _, q := range qs {
		if sq, ok := q.(*Substring); ok {
			if len(sq.Pattern) < 3 && !sq.FileName && len(qs) > 1 {
				return nil, 0, &SuggestQueryError{
					fmt.Sprintf("pattern %q too short", sq.Pattern),
					fmt.Sprintf("%q", in),
				}
			}
			switch setCase {
			case "yes":
				sq.CaseSensitive = true
			case "no":
				sq.CaseSensitive = false
			case "auto":
				sq.CaseSensitive = (sq.Pattern != string(toLower([]byte(sq.Pattern))))
			}
		}

		if sq, ok := q.(*Regexp); ok && setCase != "" {
			switch setCase {

			case "yes":
				sq.CaseSensitive = true
			case "no":
				sq.CaseSensitive = false
			case "auto":
				sq.CaseSensitive = (sq.Regexp.String() != LowerRegexp(sq.Regexp).String())
			}
		}
	}
	return qs, len(in) - len(b), nil
}

type token struct {
	Type int
	// The value of the token
	Text []byte

	// The input that we consumed to form the token.
	Input []byte
}

func (t *token) String() string {
	return fmt.Sprintf("%s:%q", tokNames[t.Type], t.Text)
}

// token types.
const (
	tokText       = 0
	tokFile       = 1
	tokRepo       = 2
	tokCase       = 3
	tokBranch     = 4
	tokParenOpen  = 5
	tokParenClose = 6
	tokError      = 7
	tokNegate     = 8
	tokRegex      = 9
	tokOr         = 10
)

var tokNames = map[int]string{
	tokText:       "Text",
	tokFile:       "File",
	tokRepo:       "Repo",
	tokCase:       "Case",
	tokBranch:     "Branch",
	tokParenOpen:  "ParenOpen",
	tokParenClose: "ParenClose",
	tokError:      "Error",
	tokNegate:     "Negate",
	tokRegex:      "Regex",
	tokOr:         "Or",
}

var prefixes = map[string]int{
	"b:":      tokBranch,
	"branch:": tokBranch,
	"case:":   tokCase,
	"f:":      tokFile,
	"file:":   tokFile,
	"r:":      tokRepo,
	"regex:":  tokRegex,
	"repo:":   tokRepo,
}

var reservedWords = map[string]int{
	"or": tokOr,
}

func (t *token) setType() {
	// After we consumed the input, we have to interpret some of the text,
	// eg. to distinguish between ")" the text and ) the query grouping
	// parenthesis.
	if len(t.Text) == 1 && t.Text[0] == '(' {
		t.Type = tokParenOpen
	}
	if len(t.Text) == 1 && t.Text[0] == ')' {
		t.Type = tokParenClose
	}

	for w, typ := range reservedWords {
		if string(t.Text) == w && string(t.Input) == w {
			t.Type = typ
			break
		}
	}

	for pref, typ := range prefixes {
		if !bytes.HasPrefix(t.Input, []byte(pref)) {
			continue
		}

		t.Text = t.Text[len(pref):]
		t.Type = typ
		break
	}
}

// nextToken returns the next token from the given input.
func nextToken(in []byte) (*token, error) {
	left := in[:]
	parenCount := 0
	var cur token
	if len(left) == 0 {
		return nil, nil
	}

	if left[0] == '-' {
		return &token{
			Type:  tokNegate,
			Text:  []byte{'-'},
			Input: in[:1],
		}, nil
	}

	foundSpace := false

loop:
	for len(left) > 0 {
		c := left[0]
		switch c {
		case '(':
			parenCount++
			cur.Text = append(cur.Text, c)
			left = left[1:]
		case ')':
			if parenCount == 0 {
				if len(cur.Text) == 0 {
					cur.Text = []byte{')'}
					left = left[1:]
				}
				break loop
			}

			cur.Text = append(cur.Text, c)
			left = left[1:]
			parenCount--

		case '"':
			t, n, err := parseStringLiteral(left)
			if err != nil {
				return nil, err
			}
			cur.Text = append(cur.Text, t...)
			left = left[n:]
		case '\\':
			left = left[1:]
			if len(left) == 0 {
				return nil, fmt.Errorf("lone \\ at end")
			}
			c := left[0]
			cur.Text = append(cur.Text, '\\', c)
			left = left[1:]

		case ' ', '\n', '\t':
			if parenCount > 0 {
				foundSpace = true
			}
			break loop
		default:
			cur.Text = append(cur.Text, c)
			left = left[1:]
		}
	}

	if len(cur.Text) == 0 {
		return nil, nil
	}

	if foundSpace && cur.Text[0] == '(' {
		cur.Text = cur.Text[:1]
		cur.Input = in[:1]
	} else {
		cur.Input = in[:len(in)-len(left)]
	}
	cur.setType()
	return &cur, nil
}
<commit_msg>query: add 'query' prefix to error messages.<commit_after>// Copyright 2016 Google Inc. All rights reserved.
//
// 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 query

import (
	"bytes"
	"fmt"
	"log"
	"regexp/syntax"
)

var _ = log.Printf

const ngramSize = 3

type SuggestQueryError struct {
	Message    string
	Suggestion string
}

func (e *SuggestQueryError) Error() string {
	return fmt.Sprintf("%s. Suggestion: %s", e.Message, e.Suggestion)
}

// parseStringLiteral parses a string literal, consumes the starting
// quote too.
func parseStringLiteral(in []byte) (lit []byte, n int, err error) {
	left := in[1:]
	found := false

loop:
	for len(left) > 0 {
		c := left[0]
		left = left[1:]
		switch c {
		case '"':
			found = true
			break loop
		case '\\':
			// TODO - other escape sequences.
			if len(left) == 0 {
				return nil, 0, fmt.Errorf("query: missing char after \\")
			}
			c = left[0]
			left = left[1:]

			lit = append(lit, c)
		default:
			lit = append(lit, c)
		}
	}
	if !found {
		return nil, 0, fmt.Errorf("query: unterminated quoted string")
	}
	return lit, len(in) - len(left), nil
}

type setCase string

// orOperator is a placeholder intermediate so we can represent [A,
// or, B] before we convert it to Or{A, B}
type orOperator struct{}

func (o *orOperator) String() string {
	return "orOp"
}

func isSpace(c byte) bool {
	return c == ' ' || c == '\t'
}

// Consumes KEYWORD:<arg>, where arg may be quoted.  Returns the
// keyword, the number of bytes consumed, whether it matched, and if
// there was an error.
func consumeKeyword(in []byte, kw []byte) ([]byte, int, bool, error) {
	if !bytes.HasPrefix(in, kw) {
		return nil, 0, false, nil
	}

	var arg []byte
	var err error
	left := in
	left = left[len(kw):]
done:
	for len(left) > 0 {
		c := left[0]
		switch {
		case c == '"':
			var n int
			arg, n, err = parseStringLiteral(left)
			if err != nil {
				return nil, 0, true, err
			}

			left = left[n:]
		case isSpace(c):
			break done
		default:
			arg = append(arg, c)
			left = left[1:]
		}
	}

	return arg, len(in) - len(left), true, nil
}

// Parse parses a string into a query.
func Parse(qStr string) (Q, error) {
	b := []byte(qStr)

	qs, _, err := parseExprList(b)
	if err != nil {
		return nil, err
	}

	q, err := parseOperators(qs)
	if err != nil {
		return nil, err
	}

	return Simplify(q), nil
}

// parseExpr parses a single expression, returning the result, and the
// number of bytes consumed.
func parseExpr(in []byte) (Q, int, error) {
	b := in[:]
	var expr Q
	for len(b) > 0 && isSpace(b[0]) {
		b = b[1:]
	}

	tok, err := nextToken(b)
	if err != nil {
		return nil, 0, err
	}
	if tok == nil {
		return nil, 0, nil
	}
	b = b[len(tok.Input):]

	text := string(tok.Text)
	switch tok.Type {
	case tokCase:
		switch text {
		case "yes":
		case "no":
		case "auto":
		default:
			return nil, 0, fmt.Errorf("query: unknown case argument %q, want {yes,no,auto}", text)
		}
		expr = &caseQ{text}
	case tokRepo:
		expr = &Repo{Pattern: text}
	case tokBranch:
		expr = &Branch{Pattern: text}
	case tokText, tokRegex:
		q, err := regexpQuery(text, false)
		if err != nil {
			return nil, 0, err
		}
		expr = q
	case tokFile:
		q, err := regexpQuery(text, true)
		if err != nil {
			return nil, 0, err
		}
		expr = q

	case tokParenClose:
		// Caller must consume paren.
		expr = nil

	case tokParenOpen:
		qs, n, err := parseExprList(b)
		b = b[n:]
		if err != nil {
			return nil, 0, err
		}

		pTok, err := nextToken(b)
		if err != nil {
			return nil, 0, err
		}
		if pTok == nil || pTok.Type != tokParenClose {
			return nil, 0, fmt.Errorf("query: missing close paren, got token %v", pTok)
		}

		b = b[len(pTok.Input):]
		expr, err = parseOperators(qs)
		if err != nil {
			return nil, 0, err
		}
	case tokNegate:
		subQ, n, err := parseExpr(b)
		if err != nil {
			return nil, 0, err
		}
		b = b[n:]
		expr = &Not{subQ}
	}

	return expr, len(in) - len(b), nil
}

// regexpQuery parses an atom into either a regular expression, or a
// simple substring atom.
func regexpQuery(text string, file bool) (Q, error) {
	var expr Q

	r, err := syntax.Parse(text, syntax.Perl)
	if err != nil {
		return nil, err
	}

	if r.Op == syntax.OpLiteral {
		expr = &Substring{
			Pattern:  string(r.Rune),
			FileName: file,
		}
	} else {
		expr = &Regexp{
			Regexp:   r,
			FileName: file,
		}
	}

	return expr, nil
}

// parseOperators interprets the orOperator in a list of queries.
func parseOperators(in []Q) (Q, error) {
	top := &Or{}
	cur := &And{}

	seenOr := false
	for _, q := range in {
		if _, ok := q.(*orOperator); ok {
			seenOr = true
			if len(cur.Children) == 0 {
				return nil, fmt.Errorf("query: OR operator should have operand")
			}
			top.Children = append(top.Children, cur)
			cur = &And{}
		} else {
			cur.Children = append(cur.Children, q)
		}
	}

	if seenOr && len(cur.Children) == 0 {
		return nil, fmt.Errorf("query: OR operator should have operand")
	}
	top.Children = append(top.Children, cur)
	return top, nil
}

// parseExprList parses a list of query expressions. It is the
// workhorse of the Parse function.
func parseExprList(in []byte) ([]Q, int, error) {
	b := in[:]
	var qs []Q
	for len(b) > 0 {
		for len(b) > 0 && isSpace(b[0]) {
			b = b[1:]
		}
		tok, _ := nextToken(b)
		if tok != nil && tok.Type == tokParenClose {
			break
		} else if tok != nil && tok.Type == tokOr {
			qs = append(qs, &orOperator{})
			b = b[len(tok.Input):]
			continue
		}

		q, n, err := parseExpr(b)
		if err != nil {
			return nil, 0, err
		}

		if q == nil {
			// eof or a ')'
			break
		}
		qs = append(qs, q)
		b = b[n:]
	}

	setCase := "auto"
	newQS := qs[:0]
	for _, q := range qs {
		if sc, ok := q.(*caseQ); ok {
			setCase = sc.Flavor
		} else {
			newQS = append(newQS, q)
		}
	}
	qs = newQS
	for _, q := range qs {
		if sq, ok := q.(*Substring); ok {
			if len(sq.Pattern) < 3 && !sq.FileName && len(qs) > 1 {
				return nil, 0, &SuggestQueryError{
					fmt.Sprintf("pattern %q too short", sq.Pattern),
					fmt.Sprintf("%q", in),
				}
			}
			switch setCase {
			case "yes":
				sq.CaseSensitive = true
			case "no":
				sq.CaseSensitive = false
			case "auto":
				sq.CaseSensitive = (sq.Pattern != string(toLower([]byte(sq.Pattern))))
			}
		}

		if sq, ok := q.(*Regexp); ok && setCase != "" {
			switch setCase {

			case "yes":
				sq.CaseSensitive = true
			case "no":
				sq.CaseSensitive = false
			case "auto":
				sq.CaseSensitive = (sq.Regexp.String() != LowerRegexp(sq.Regexp).String())
			}
		}
	}
	return qs, len(in) - len(b), nil
}

type token struct {
	Type int
	// The value of the token
	Text []byte

	// The input that we consumed to form the token.
	Input []byte
}

func (t *token) String() string {
	return fmt.Sprintf("%s:%q", tokNames[t.Type], t.Text)
}

// token types.
const (
	tokText       = 0
	tokFile       = 1
	tokRepo       = 2
	tokCase       = 3
	tokBranch     = 4
	tokParenOpen  = 5
	tokParenClose = 6
	tokError      = 7
	tokNegate     = 8
	tokRegex      = 9
	tokOr         = 10
)

var tokNames = map[int]string{
	tokText:       "Text",
	tokFile:       "File",
	tokRepo:       "Repo",
	tokCase:       "Case",
	tokBranch:     "Branch",
	tokParenOpen:  "ParenOpen",
	tokParenClose: "ParenClose",
	tokError:      "Error",
	tokNegate:     "Negate",
	tokRegex:      "Regex",
	tokOr:         "Or",
}

var prefixes = map[string]int{
	"b:":      tokBranch,
	"branch:": tokBranch,
	"case:":   tokCase,
	"f:":      tokFile,
	"file:":   tokFile,
	"r:":      tokRepo,
	"regex:":  tokRegex,
	"repo:":   tokRepo,
}

var reservedWords = map[string]int{
	"or": tokOr,
}

func (t *token) setType() {
	// After we consumed the input, we have to interpret some of the text,
	// eg. to distinguish between ")" the text and ) the query grouping
	// parenthesis.
	if len(t.Text) == 1 && t.Text[0] == '(' {
		t.Type = tokParenOpen
	}
	if len(t.Text) == 1 && t.Text[0] == ')' {
		t.Type = tokParenClose
	}

	for w, typ := range reservedWords {
		if string(t.Text) == w && string(t.Input) == w {
			t.Type = typ
			break
		}
	}

	for pref, typ := range prefixes {
		if !bytes.HasPrefix(t.Input, []byte(pref)) {
			continue
		}

		t.Text = t.Text[len(pref):]
		t.Type = typ
		break
	}
}

// nextToken returns the next token from the given input.
func nextToken(in []byte) (*token, error) {
	left := in[:]
	parenCount := 0
	var cur token
	if len(left) == 0 {
		return nil, nil
	}

	if left[0] == '-' {
		return &token{
			Type:  tokNegate,
			Text:  []byte{'-'},
			Input: in[:1],
		}, nil
	}

	foundSpace := false

loop:
	for len(left) > 0 {
		c := left[0]
		switch c {
		case '(':
			parenCount++
			cur.Text = append(cur.Text, c)
			left = left[1:]
		case ')':
			if parenCount == 0 {
				if len(cur.Text) == 0 {
					cur.Text = []byte{')'}
					left = left[1:]
				}
				break loop
			}

			cur.Text = append(cur.Text, c)
			left = left[1:]
			parenCount--

		case '"':
			t, n, err := parseStringLiteral(left)
			if err != nil {
				return nil, err
			}
			cur.Text = append(cur.Text, t...)
			left = left[n:]
		case '\\':
			left = left[1:]
			if len(left) == 0 {
				return nil, fmt.Errorf("query: lone \\ at end")
			}
			c := left[0]
			cur.Text = append(cur.Text, '\\', c)
			left = left[1:]

		case ' ', '\n', '\t':
			if parenCount > 0 {
				foundSpace = true
			}
			break loop
		default:
			cur.Text = append(cur.Text, c)
			left = left[1:]
		}
	}

	if len(cur.Text) == 0 {
		return nil, nil
	}

	if foundSpace && cur.Text[0] == '(' {
		cur.Text = cur.Text[:1]
		cur.Input = in[:1]
	} else {
		cur.Input = in[:len(in)-len(left)]
	}
	cur.setType()
	return &cur, nil
}
<|endoftext|>


<commit_before>// Copyright 2016 Google Inc. All rights reserved.
//
// 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 query

import (
	"fmt"
	"log"
	"reflect"
	"regexp/syntax"
	"strings"
)

var _ = log.Println

// Q is a representation for a possibly hierarchical search query.
type Q interface {
	String() string
}

// RegexpQuery is a query looking for regular expressions matches.
type Regexp struct {
	Regexp        *syntax.Regexp
	FileName      bool
	Content       bool
	CaseSensitive bool
}

func (q *Regexp) String() string {
	pref := ""
	if q.FileName {
		pref = "file_"
	}
	if q.CaseSensitive {
		pref = "case_" + pref
	}
	return fmt.Sprintf("%sregex:%q", pref, q.Regexp.String())
}

type caseQ struct {
	Flavor string
}

func (c *caseQ) String() string {
	return "case:" + c.Flavor
}

type Language struct {
	Language string
}

func (l *Language) String() string {
	return "lang:" + l.Language
}

type Const struct {
	Value bool
}

func (q *Const) String() string {
	if q.Value {
		return "TRUE"
	}
	return "FALSE"
}

type Repo struct {
	Pattern string
}

func (q *Repo) String() string {
	return fmt.Sprintf("repo:%s", q.Pattern)
}

// Substring is the most basic query: a query for a substring.
type Substring struct {
	Pattern       string
	CaseSensitive bool

	// Match only filename
	FileName bool

	// Match only content
	Content bool
}

func (q *Substring) String() string {
	s := ""

	t := ""
	if q.FileName {
		t = "file_"
	} else if q.Content {
		t = "content_"
	}

	s += fmt.Sprintf("%ssubstr:%q", t, q.Pattern)
	if q.CaseSensitive {
		s = "case_" + s
	}
	return s
}

// Or is matched when any of its children is matched.
type Or struct {
	Children []Q
}

func (q *Or) String() string {
	var sub []string
	for _, ch := range q.Children {
		sub = append(sub, ch.String())
	}
	return fmt.Sprintf("(or %s)", strings.Join(sub, " "))
}

// Not inverts the meaning of its child.
type Not struct {
	Child Q
}

func (q *Not) String() string {
	return fmt.Sprintf("(not %s)", q.Child)
}

// And is matched when all its children are.
type And struct {
	Children []Q
}

func (q *And) String() string {
	var sub []string
	for _, ch := range q.Children {
		sub = append(sub, ch.String())
	}
	return fmt.Sprintf("(and %s)", strings.Join(sub, " "))
}

// NewAnd is syntactic sugar for constructing And queries.
func NewAnd(qs ...Q) Q {
	return &And{Children: qs}
}

// NewOr is syntactic sugar for constructing Or queries.
func NewOr(qs ...Q) Q {
	return &Or{Children: qs}
}

// Branch limits search to a specific branch.
type Branch struct {
	Pattern string
}

func (q *Branch) String() string {
	return fmt.Sprintf("branch:%q", q.Pattern)
}

func queryChildren(q Q) []Q {
	switch s := q.(type) {
	case *And:
		return s.Children
	case *Or:
		return s.Children
	}
	return nil
}

func flattenAndOr(children []Q, typ Q) ([]Q, bool) {
	var flat []Q
	changed := false
	for _, ch := range children {
		ch, subChanged := flatten(ch)
		changed = changed || subChanged
		if reflect.TypeOf(ch) == reflect.TypeOf(typ) {
			changed = true
			subChildren := queryChildren(ch)
			if subChildren != nil {
				flat = append(flat, subChildren...)
			}
		} else {
			flat = append(flat, ch)
		}
	}

	return flat, changed
}

// (and (and x y) z) => (and x y z) , the same for "or"
func flatten(q Q) (Q, bool) {
	switch s := q.(type) {
	case *And:
		if len(s.Children) == 1 {
			return s.Children[0], true
		}
		flatChildren, changed := flattenAndOr(s.Children, s)
		return &And{flatChildren}, changed
	case *Or:
		if len(s.Children) == 1 {
			return s.Children[0], true
		}
		flatChildren, changed := flattenAndOr(s.Children, s)
		return &Or{flatChildren}, changed
	case *Not:
		child, changed := flatten(s.Child)
		return &Not{child}, changed
	default:
		return q, false
	}
}

func mapQueryList(qs []Q, f func(Q) Q) []Q {
	var neg []Q
	for _, sub := range qs {
		neg = append(neg, Map(sub, f))
	}
	return neg
}

func invertConst(q Q) Q {
	c, ok := q.(*Const)
	if ok {
		return &Const{!c.Value}
	}
	return q
}

func evalAndOrConstants(q Q, children []Q) Q {
	_, isAnd := q.(*And)

	children = mapQueryList(children, evalConstants)

	newCH := children[:0]
	for _, ch := range children {
		c, ok := ch.(*Const)
		if ok {
			if c.Value == isAnd {
				continue
			} else {
				return ch
			}
		}
		newCH = append(newCH, ch)
	}
	if len(newCH) == 0 {
		return &Const{isAnd}
	}
	if isAnd {
		return &And{newCH}
	}
	return &Or{newCH}
}

func evalConstants(q Q) Q {
	switch s := q.(type) {
	case *And:
		return evalAndOrConstants(q, s.Children)
	case *Or:
		return evalAndOrConstants(q, s.Children)
	case *Not:
		ch := evalConstants(s.Child)
		if _, ok := ch.(*Const); ok {
			return invertConst(ch)
		}
		return &Not{ch}
	case *Substring:
		if len(s.Pattern) == 0 {
			return &Const{true}
		}
	case *Regexp:
		if s.Regexp.Op == syntax.OpEmptyMatch {
			return &Const{true}
		}
	}
	return q
}

func Simplify(q Q) Q {
	q = evalConstants(q)
	for {
		var changed bool
		q, changed = flatten(q)
		if !changed {
			break
		}
	}

	return q
}

// Map runs f over the q.
func Map(q Q, f func(q Q) Q) Q {
	switch s := q.(type) {
	case *And:
		q = &And{Children: mapQueryList(s.Children, f)}
	case *Or:
		q = &Or{Children: mapQueryList(s.Children, f)}
	case *Not:
		q = &Not{Child: Map(s.Child, f)}
	}
	return f(q)
}

// Expand expands Substr queries into (OR file_substr content_substr)
// queries, and the same for Regexp queries..
func ExpandFileContent(q Q) Q {
	switch s := q.(type) {
	case *Substring:
		if !s.FileName && !s.Content {
			f := *s
			f.FileName = true
			c := *s
			c.Content = true
			return NewOr(&f, &c)
		}
	case *Regexp:
		if !s.FileName && !s.Content {
			f := *s
			f.FileName = true
			c := *s
			c.Content = true
			return NewOr(&f, &c)
		}
	}
	return q
}

// VisitAtoms runs `v` on all atom queries within `q`.
func VisitAtoms(q Q, v func(q Q)) {
	Map(q, func(iQ Q) Q {
		switch iQ.(type) {
		case *And:
		case *Or:
		case *Not:
		default:
			v(iQ)
		}
		return iQ
	})
}
<commit_msg>query: Simplify empty branch and repo patterns<commit_after>// Copyright 2016 Google Inc. All rights reserved.
//
// 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 query

import (
	"fmt"
	"log"
	"reflect"
	"regexp/syntax"
	"strings"
)

var _ = log.Println

// Q is a representation for a possibly hierarchical search query.
type Q interface {
	String() string
}

// RegexpQuery is a query looking for regular expressions matches.
type Regexp struct {
	Regexp        *syntax.Regexp
	FileName      bool
	Content       bool
	CaseSensitive bool
}

func (q *Regexp) String() string {
	pref := ""
	if q.FileName {
		pref = "file_"
	}
	if q.CaseSensitive {
		pref = "case_" + pref
	}
	return fmt.Sprintf("%sregex:%q", pref, q.Regexp.String())
}

type caseQ struct {
	Flavor string
}

func (c *caseQ) String() string {
	return "case:" + c.Flavor
}

type Language struct {
	Language string
}

func (l *Language) String() string {
	return "lang:" + l.Language
}

type Const struct {
	Value bool
}

func (q *Const) String() string {
	if q.Value {
		return "TRUE"
	}
	return "FALSE"
}

type Repo struct {
	Pattern string
}

func (q *Repo) String() string {
	return fmt.Sprintf("repo:%s", q.Pattern)
}

// Substring is the most basic query: a query for a substring.
type Substring struct {
	Pattern       string
	CaseSensitive bool

	// Match only filename
	FileName bool

	// Match only content
	Content bool
}

func (q *Substring) String() string {
	s := ""

	t := ""
	if q.FileName {
		t = "file_"
	} else if q.Content {
		t = "content_"
	}

	s += fmt.Sprintf("%ssubstr:%q", t, q.Pattern)
	if q.CaseSensitive {
		s = "case_" + s
	}
	return s
}

// Or is matched when any of its children is matched.
type Or struct {
	Children []Q
}

func (q *Or) String() string {
	var sub []string
	for _, ch := range q.Children {
		sub = append(sub, ch.String())
	}
	return fmt.Sprintf("(or %s)", strings.Join(sub, " "))
}

// Not inverts the meaning of its child.
type Not struct {
	Child Q
}

func (q *Not) String() string {
	return fmt.Sprintf("(not %s)", q.Child)
}

// And is matched when all its children are.
type And struct {
	Children []Q
}

func (q *And) String() string {
	var sub []string
	for _, ch := range q.Children {
		sub = append(sub, ch.String())
	}
	return fmt.Sprintf("(and %s)", strings.Join(sub, " "))
}

// NewAnd is syntactic sugar for constructing And queries.
func NewAnd(qs ...Q) Q {
	return &And{Children: qs}
}

// NewOr is syntactic sugar for constructing Or queries.
func NewOr(qs ...Q) Q {
	return &Or{Children: qs}
}

// Branch limits search to a specific branch.
type Branch struct {
	Pattern string
}

func (q *Branch) String() string {
	return fmt.Sprintf("branch:%q", q.Pattern)
}

func queryChildren(q Q) []Q {
	switch s := q.(type) {
	case *And:
		return s.Children
	case *Or:
		return s.Children
	}
	return nil
}

func flattenAndOr(children []Q, typ Q) ([]Q, bool) {
	var flat []Q
	changed := false
	for _, ch := range children {
		ch, subChanged := flatten(ch)
		changed = changed || subChanged
		if reflect.TypeOf(ch) == reflect.TypeOf(typ) {
			changed = true
			subChildren := queryChildren(ch)
			if subChildren != nil {
				flat = append(flat, subChildren...)
			}
		} else {
			flat = append(flat, ch)
		}
	}

	return flat, changed
}

// (and (and x y) z) => (and x y z) , the same for "or"
func flatten(q Q) (Q, bool) {
	switch s := q.(type) {
	case *And:
		if len(s.Children) == 1 {
			return s.Children[0], true
		}
		flatChildren, changed := flattenAndOr(s.Children, s)
		return &And{flatChildren}, changed
	case *Or:
		if len(s.Children) == 1 {
			return s.Children[0], true
		}
		flatChildren, changed := flattenAndOr(s.Children, s)
		return &Or{flatChildren}, changed
	case *Not:
		child, changed := flatten(s.Child)
		return &Not{child}, changed
	default:
		return q, false
	}
}

func mapQueryList(qs []Q, f func(Q) Q) []Q {
	var neg []Q
	for _, sub := range qs {
		neg = append(neg, Map(sub, f))
	}
	return neg
}

func invertConst(q Q) Q {
	c, ok := q.(*Const)
	if ok {
		return &Const{!c.Value}
	}
	return q
}

func evalAndOrConstants(q Q, children []Q) Q {
	_, isAnd := q.(*And)

	children = mapQueryList(children, evalConstants)

	newCH := children[:0]
	for _, ch := range children {
		c, ok := ch.(*Const)
		if ok {
			if c.Value == isAnd {
				continue
			} else {
				return ch
			}
		}
		newCH = append(newCH, ch)
	}
	if len(newCH) == 0 {
		return &Const{isAnd}
	}
	if isAnd {
		return &And{newCH}
	}
	return &Or{newCH}
}

func evalConstants(q Q) Q {
	switch s := q.(type) {
	case *And:
		return evalAndOrConstants(q, s.Children)
	case *Or:
		return evalAndOrConstants(q, s.Children)
	case *Not:
		ch := evalConstants(s.Child)
		if _, ok := ch.(*Const); ok {
			return invertConst(ch)
		}
		return &Not{ch}
	case *Substring:
		if len(s.Pattern) == 0 {
			return &Const{true}
		}
	case *Regexp:
		if s.Regexp.Op == syntax.OpEmptyMatch {
			return &Const{true}
		}
	case *Branch:
		if s.Pattern == "" {
			return &Const{true}
		}
	case *Repo:
		if s.Pattern == "" {
			return &Const{true}
		}
	}
	return q
}

func Simplify(q Q) Q {
	q = evalConstants(q)
	for {
		var changed bool
		q, changed = flatten(q)
		if !changed {
			break
		}
	}

	return q
}

// Map runs f over the q.
func Map(q Q, f func(q Q) Q) Q {
	switch s := q.(type) {
	case *And:
		q = &And{Children: mapQueryList(s.Children, f)}
	case *Or:
		q = &Or{Children: mapQueryList(s.Children, f)}
	case *Not:
		q = &Not{Child: Map(s.Child, f)}
	}
	return f(q)
}

// Expand expands Substr queries into (OR file_substr content_substr)
// queries, and the same for Regexp queries..
func ExpandFileContent(q Q) Q {
	switch s := q.(type) {
	case *Substring:
		if !s.FileName && !s.Content {
			f := *s
			f.FileName = true
			c := *s
			c.Content = true
			return NewOr(&f, &c)
		}
	case *Regexp:
		if !s.FileName && !s.Content {
			f := *s
			f.FileName = true
			c := *s
			c.Content = true
			return NewOr(&f, &c)
		}
	}
	return q
}

// VisitAtoms runs `v` on all atom queries within `q`.
func VisitAtoms(q Q, v func(q Q)) {
	Map(q, func(iQ Q) Q {
		switch iQ.(type) {
		case *And:
		case *Or:
		case *Not:
		default:
			v(iQ)
		}
		return iQ
	})
}
<|endoftext|>


<commit_before>package quote

import (
	"bytes"
	"encoding/xml"
	"fmt"
	log "github.com/Sirupsen/logrus"
	"github.com/xtracdev/xavi/plugin"
	"github.com/xtracdev/xavi/plugin/timing"
	"github.com/xtracdev/xavisample/session"
	"golang.org/x/net/context"
	"io/ioutil"
	"math/rand"
	"net/http"
	"net/http/httptest"
	"strings"
	"time"
	"errors"
)

func extractResource(uri string) (string, error) {
	parts := strings.Split(uri, "/")
	if len(parts) != 3 || parts[2] == "" {
		return "", fmt.Errorf("Expected URI format: /quote/<symbol>")
	}

	return parts[2], nil

}

func NewQuoteWrapper() plugin.Wrapper {
	return new(QuoteWrapper)
}

//For use in generating a variety of service names for use in exploring log management solutions,
//we'll randomly generate a service name.

var serviceNames = []string{"alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf", "india", "hotel"}

func generateServiceName() string {
	return serviceNames[rand.Intn(len(serviceNames))]
}

type QuoteWrapper struct{}

func (lw QuoteWrapper) Wrap(h plugin.ContextHandler) plugin.ContextHandler {
	return plugin.ContextHandlerFunc(func(c context.Context, w http.ResponseWriter, r *http.Request) {

		//Extract the timer from the service context
		end2endTimer := timing.TimerFromContext(c)
		if end2endTimer == nil {
			http.Error(w, "No timer in call context", http.StatusInternalServerError)
			return
		}

		//Set the top level name we want to use for recording timings, counts, etc.
		end2endTimer.Name = fmt.Sprintf("%s-quote", generateServiceName())

		contributor := end2endTimer.StartContributor("QuoteSvc.GetTradePrice")

		//Grab the symbol to quote from the uri
		resourceId, err := extractResource(r.RequestURI)
		if err != nil {
			contributor.End(err)
			w.WriteHeader(http.StatusNotFound)
			w.Write([]byte(err.Error()))
			return
		}

		if resourceId == "XTRAC" {
			panic(errors.New("priceless!"))
		}

		if c != nil {
			sid, ok := c.Value(session.SessionKey).(int)
			if ok {
				log.Println("session:", sid, "symbol", resourceId)
			}
		}

		//Convert the method to POST for SOAP, and set the soap service
		//endpoint for the destination server
		r.Method = "POST"
		r.URL.Path = "/services/quote/getquote"

		//Form the SOAP payload
		payload := getQuoteRequestForSymbol(resourceId)
		payloadBytes, err := xml.Marshal(&payload)
		if err != nil {
			contributor.End(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		//Post the payload, and record the response
		r.Body = ioutil.NopCloser(bytes.NewReader(payloadBytes))
		rec := httptest.NewRecorder()

		c = timing.AddServiceNameToContext(c, "QuoteSoapService")
		h.ServeHTTPContext(c, rec, r)

		//Throw in a random service delay
		delay := rand.Intn(100) + 1
		time.Sleep(time.Duration(delay) * time.Millisecond)

		//Parse the recorded response to allow the quote price to be extracted
		var response ResponseEnvelope
		err = xml.Unmarshal(rec.Body.Bytes(), &response)
		if err != nil {
			contributor.End(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		//Return just the price to the caller
		w.Write([]byte(response.Body.GetLastTradePriceResponse.Price + "\n"))
		contributor.End(nil)
	})
}
<commit_msg>Updated to allow random call cancellation to be injected<commit_after>package quote

import (
	"bytes"
	"encoding/xml"
	"errors"
	"fmt"
	log "github.com/Sirupsen/logrus"
	"github.com/xtracdev/xavi/plugin"
	"github.com/xtracdev/xavi/plugin/timing"
	"github.com/xtracdev/xavisample/session"
	"golang.org/x/net/context"
	"io/ioutil"
	"math/rand"
	"net/http"
	"net/http/httptest"
	"os"
	"strings"
	"sync"
	"time"
)

func extractResource(uri string) (string, error) {
	parts := strings.Split(uri, "/")
	if len(parts) != 3 || parts[2] == "" {
		return "", fmt.Errorf("Expected URI format: /quote/<symbol>")
	}

	return parts[2], nil

}

func NewQuoteWrapper() plugin.Wrapper {
	return new(QuoteWrapper)
}

//For use in generating a variety of service names for use in exploring log management solutions,
//we'll randomly generate a service name.

var serviceNames = []string{"alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf", "india", "hotel"}

func generateServiceName() string {
	return serviceNames[rand.Intn(len(serviceNames))]
}

type QuoteWrapper struct{}

func (lw QuoteWrapper) Wrap(h plugin.ContextHandler) plugin.ContextHandler {
	return plugin.ContextHandlerFunc(func(c context.Context, w http.ResponseWriter, r *http.Request) {

		//Extract the timer from the service context
		end2endTimer := timing.TimerFromContext(c)
		if end2endTimer == nil {
			http.Error(w, "No timer in call context", http.StatusInternalServerError)
			return
		}

		//Set the top level name we want to use for recording timings, counts, etc.
		end2endTimer.Name = fmt.Sprintf("%s-quote", generateServiceName())

		contributor := end2endTimer.StartContributor("QuoteSvc.GetTradePrice")

		//Grab the symbol to quote from the uri
		resourceId, err := extractResource(r.RequestURI)
		if err != nil {
			contributor.End(err)
			w.WriteHeader(http.StatusNotFound)
			w.Write([]byte(err.Error()))
			return
		}

		if resourceId == "XTRAC" {
			panic(errors.New("priceless!"))
		}

		if c != nil {
			sid, ok := c.Value(session.SessionKey).(int)
			if ok {
				log.Println("session:", sid, "symbol", resourceId)
			}
		}

		//Convert the method to POST for SOAP, and set the soap service
		//endpoint for the destination server
		r.Method = "POST"
		r.URL.Path = "/services/quote/getquote"

		//Form the SOAP payload
		payload := getQuoteRequestForSymbol(resourceId)
		payloadBytes, err := xml.Marshal(&payload)
		if err != nil {
			contributor.End(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		//Post the payload, and record the response
		r.Body = ioutil.NopCloser(bytes.NewReader(payloadBytes))
		rec := httptest.NewRecorder()

		c = timing.AddServiceNameToContext(c, "QuoteSoapService")

		c, cf := context.WithCancel(c)

		var wg sync.WaitGroup
		wg.Add(1)
		go func() {
			defer wg.Done()
			h.ServeHTTPContext(c, rec, r)
		}()

		maybeCancel(cf)

		wg.Wait()

		//Throw in a random service delay
		delay := rand.Intn(100) + 1
		time.Sleep(time.Duration(delay) * time.Millisecond)

		//Was there an error?
		if rec.Code > 299 {
			log.Println("damn cuz something wrong")
			w.WriteHeader(http.StatusInternalServerError)
			w.Write(rec.Body.Bytes())
			contributor.End(err)
			return
		}

		//Parse the recorded response to allow the quote price to be extracted
		var response ResponseEnvelope
		err = xml.Unmarshal(rec.Body.Bytes(), &response)
		if err != nil {
			contributor.End(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		//Return just the price to the caller
		w.Write([]byte(response.Body.GetLastTradePriceResponse.Price + "\n"))
		contributor.End(nil)
	})
}

func maybeCancel(cf context.CancelFunc) {
	if os.Getenv("MAYBE_CANCEL") == "" {
		return
	}

	//Flip a coin
	if rand.Float64() > 0.5 {
		log.Println("coin toss says cancel")
		cf()
	}
}
<|endoftext|>


<commit_before>package yenc

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"testing"
)

func TestDecoder(t *testing.T) {
	encoded, err := os.Open("./test/00000005.ntx")

	if err != nil {
		t.Fatalf("could not begin test: ntx file not readable")
	}

	unencoded, err := ioutil.ReadFile("./test/testfile.txt")

	if err != nil {
		t.Fatalf("could not read unencoded test file")
	}

	decBytes, err := ioutil.ReadAll(NewReader(encoded))

	if err != nil {
		t.Fatalf("Error reading decoded bytes: %v", err)
	}

	if !bytes.Equal(decBytes, unencoded) {
		fmt.Println(hex.Dump(decBytes))

		diff := getDiff(unencoded, decBytes)
		t.Errorf("Decoded bytes did not equal unencoded bytes. Diff was %d long; dec was %d long", len(diff), len(decBytes))
	}
}

func BenchmarkDecoder(b *testing.B) {
	encoded, err := os.Open("./test/00000005.ntx")

	if err != nil {
		b.Fatalf("could not begin test: ntx file not readable")
	}

	bs, err := ioutil.ReadAll(encoded)
	if err != nil {
		log.Fatal(err)
	}

	buf := bytes.NewBuffer(bs)

	empty := make([]byte, 2<<20)

	for i := 0; i < b.N; i++ {
		NewReader(buf).Read(empty)
	}
}

func getDiff(b1, b2 []byte) []byte {
	b1c := make([]byte, len(b1))
	copy(b1c, b1)

	b2c := make([]byte, len(b2))
	copy(b2c, b2)

	for len(b1c) > 0 && len(b2c) > 0 && b1c[0] == b2c[0] {
		b1c = b1c[1:]
		b2c = b2c[1:]
	}

	if len(b1c) > 0 {
		return b1c
	}

	return b2c
}
<commit_msg>Add mb/s benchmark stats<commit_after>package yenc

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"testing"
)

func TestDecoder(t *testing.T) {
	encoded, err := os.Open("./test/00000005.ntx")

	if err != nil {
		t.Fatalf("could not begin test: ntx file not readable")
	}

	unencoded, err := ioutil.ReadFile("./test/testfile.txt")

	if err != nil {
		t.Fatalf("could not read unencoded test file")
	}

	decBytes, err := ioutil.ReadAll(NewReader(encoded))

	if err != nil {
		t.Fatalf("Error reading decoded bytes: %v", err)
	}

	if !bytes.Equal(decBytes, unencoded) {
		fmt.Println(hex.Dump(decBytes))

		diff := getDiff(unencoded, decBytes)
		t.Errorf("Decoded bytes did not equal unencoded bytes. Diff was %d long; dec was %d long", len(diff), len(decBytes))
	}
}

func BenchmarkDecoder(b *testing.B) {
	encoded, err := os.Open("./test/00000005.ntx")

	if err != nil {
		b.Fatalf("could not begin test: ntx file not readable")
	}

	bs, err := ioutil.ReadAll(encoded)
	if err != nil {
		log.Fatal(err)
	}

	buf := bytes.NewBuffer(bs)

	empty := make([]byte, 2<<20)

	b.SetBytes(int64(len(bs)))

	for i := 0; i < b.N; i++ {
		NewReader(buf).Read(empty)
	}
}

func getDiff(b1, b2 []byte) []byte {
	b1c := make([]byte, len(b1))
	copy(b1c, b1)

	b2c := make([]byte, len(b2))
	copy(b2c, b2)

	for len(b1c) > 0 && len(b2c) > 0 && b1c[0] == b2c[0] {
		b1c = b1c[1:]
		b2c = b2c[1:]
	}

	if len(b1c) > 0 {
		return b1c
	}

	return b2c
}
<|endoftext|>


<commit_before>package rtcp

import (
	"bytes"
	"reflect"
	"testing"

	"github.com/stretchr/testify/assert"
)

// An RTCP packet from a packet dump
var realPacket = []byte{
	// Receiver Report (offset=0)
	// v=2, p=0, count=1, RR, len=7
	0x81, 0xc9, 0x0, 0x7,
	// ssrc=0x902f9e2e
	0x90, 0x2f, 0x9e, 0x2e,
	// ssrc=0xbc5e9a40
	0xbc, 0x5e, 0x9a, 0x40,
	// fracLost=0, totalLost=0
	0x0, 0x0, 0x0, 0x0,
	// lastSeq=0x46e1
	0x0, 0x0, 0x46, 0xe1,
	// jitter=273
	0x0, 0x0, 0x1, 0x11,
	// lsr=0x9f36432
	0x9, 0xf3, 0x64, 0x32,
	// delay=150137
	0x0, 0x2, 0x4a, 0x79,

	// Source Description (offset=32)
	// v=2, p=0, count=1, SDES, len=12
	0x81, 0xca, 0x0, 0xc,
	// ssrc=0x902f9e2e
	0x90, 0x2f, 0x9e, 0x2e,
	// CNAME, len=38
	0x1, 0x26,
	// text="{9c00eb92-1afb-9d49-a47d-91f64eee69f5}"
	0x7b, 0x39, 0x63, 0x30,
	0x30, 0x65, 0x62, 0x39,
	0x32, 0x2d, 0x31, 0x61,
	0x66, 0x62, 0x2d, 0x39,
	0x64, 0x34, 0x39, 0x2d,
	0x61, 0x34, 0x37, 0x64,
	0x2d, 0x39, 0x31, 0x66,
	0x36, 0x34, 0x65, 0x65,
	0x65, 0x36, 0x39, 0x66,
	0x35, 0x7d,
	// END + padding
	0x0, 0x0, 0x0, 0x0,

	// Goodbye (offset=84)
	// v=2, p=0, count=1, BYE, len=1
	0x81, 0xcb, 0x0, 0x1,
	// source=0x902f9e2e
	0x90, 0x2f, 0x9e, 0x2e,

	// Picture Loss Indication (offset=92)
	0x81, 0xce, 0x0, 0x2,
	// sender=0x902f9e2e
	0x90, 0x2f, 0x9e, 0x2e,
	// media=0x902f9e2e
	0x90, 0x2f, 0x9e, 0x2e,

	// RapidResynchronizationRequest (offset=104)
	0x85, 0xcd, 0x0, 0x2,
	// sender=0x902f9e2e
	0x90, 0x2f, 0x9e, 0x2e,
	// media=0x902f9e2e
	0x90, 0x2f, 0x9e, 0x2e,
}

func TestUnmarshal(t *testing.T) {
	decoder := NewDecoder(bytes.NewReader(realPacket))
	packets, err := Unmarshal(realPacket)

	if err != nil {
		t.Fatalf("Error unmarshalling packets: %s", err)
	}

	if len(packets) != 5 {
		t.Fatalf("Read the wrong number of packets from input array")
	}

	// ReceiverReport
	packet := packets[0]
	decoded, err := decoder.DecodePacket()
	assert.NoError(t, err)
	assert.IsType(t, packet, (*ReceiverReport)(nil), "Unmarshalled to incorrect type")

	wantRR := &ReceiverReport{
		SSRC: 0x902f9e2e,
		Reports: []ReceptionReport{{
			SSRC:               0xbc5e9a40,
			FractionLost:       0,
			TotalLost:          0,
			LastSequenceNumber: 0x46e1,
			Jitter:             273,
			LastSenderReport:   0x9f36432,
			Delay:              150137,
		}},
		ProfileExtensions: []byte{},
	}
	if want, got := wantRR, packet; !reflect.DeepEqual(got, want) {
		t.Errorf("Unmarshal rr: got %#v, want %#v", got, want)
	}
	if want, got := wantRR, decoded; !reflect.DeepEqual(got, want) {
		t.Errorf("Unmarshal rr: got %#v, want %#v", got, want)
	}

	// SourceDescription
	packet = packets[1]
	decoded, err = decoder.DecodePacket()
	assert.NoError(t, err)
	assert.IsType(t, packet, (*SourceDescription)(nil), "Unmarshalled to incorrect type")

	wantSdes := &SourceDescription{
		Chunks: []SourceDescriptionChunk{
			{
				Source: 0x902f9e2e,
				Items: []SourceDescriptionItem{
					{
						Type: SDESCNAME,
						Text: "{9c00eb92-1afb-9d49-a47d-91f64eee69f5}",
					},
				},
			},
		},
	}

	if got, want := packet, wantSdes; !reflect.DeepEqual(got, want) {
		t.Errorf("Unmarshal sdes: got %#v, want %#v", got, want)
	}
	if got, want := decoded, wantSdes; !reflect.DeepEqual(got, want) {
		t.Errorf("Unmarshal sdes: got %#v, want %#v", got, want)
	}

	// Goodbye
	packet = packets[2]
	decoded, err = decoder.DecodePacket()
	assert.NoError(t, err)
	assert.IsType(t, packet, (*Goodbye)(nil), "Unmarshalled to incorrect type")

	wantBye := &Goodbye{
		Sources: []uint32{0x902f9e2e},
	}
	if got, want := packet, wantBye; !reflect.DeepEqual(got, want) {
		t.Errorf("Unmarshal bye: got %#v, want %#v", got, want)
	}
	if got, want := decoded, wantBye; !reflect.DeepEqual(got, want) {
		t.Errorf("Unmarshal bye: got %#v, want %#v", got, want)
	}

	// PictureLossIndication
	packet = packets[3]
	decoded, err = decoder.DecodePacket()
	assert.NoError(t, err)
	assert.IsType(t, packet, (*PictureLossIndication)(nil), "Unmarshalled to incorrect type")

	wantPli := &PictureLossIndication{
		SenderSSRC: 0x902f9e2e,
		MediaSSRC:  0x902f9e2e,
	}
	if got, want := packet, wantPli; !reflect.DeepEqual(got, want) {
		t.Errorf("Unmarshal pli: got %#v, want %#v", got, want)
	}
	if got, want := decoded, wantPli; !reflect.DeepEqual(got, want) {
		t.Errorf("Unmarshal pli: got %#v, want %#v", got, want)
	}

	// RapidResynchronizationRequest
	packet = packets[4]
	decoded, err = decoder.DecodePacket()
	assert.NoError(t, err)
	assert.IsType(t, packet, (*RapidResynchronizationRequest)(nil), "Unmarshalled to incorrect type")

	wantRrr := &RapidResynchronizationRequest{
		SenderSSRC: 0x902f9e2e,
		MediaSSRC:  0x902f9e2e,
	}
	if got, want := packet, wantRrr; !reflect.DeepEqual(got, want) {
		t.Errorf("Unmarshal rrr: got %#v, want %#v", got, want)
	}
	if got, want := decoded, wantRrr; !reflect.DeepEqual(got, want) {
		t.Errorf("Unmarshal rrr: got %#v, want %#v", got, want)
	}
}

func TestReadEOF(t *testing.T) {
	shortHeader := []byte{
		0x81, 0xc9, // missing type & len
	}

	_, err := Unmarshal(shortHeader)
	if got, want := err, errPacketTooShort; got != want {
		t.Fatalf("read short header: got err = %v, want %v", got, want)
	}
}
<commit_msg>Add some decoder tests<commit_after>package rtcp

import (
	"bytes"
	"reflect"
	"testing"

	"github.com/stretchr/testify/assert"
)

// An RTCP packet from a packet dump
var realPacket = []byte{
	// Receiver Report (offset=0)
	// v=2, p=0, count=1, RR, len=7
	0x81, 0xc9, 0x0, 0x7,
	// ssrc=0x902f9e2e
	0x90, 0x2f, 0x9e, 0x2e,
	// ssrc=0xbc5e9a40
	0xbc, 0x5e, 0x9a, 0x40,
	// fracLost=0, totalLost=0
	0x0, 0x0, 0x0, 0x0,
	// lastSeq=0x46e1
	0x0, 0x0, 0x46, 0xe1,
	// jitter=273
	0x0, 0x0, 0x1, 0x11,
	// lsr=0x9f36432
	0x9, 0xf3, 0x64, 0x32,
	// delay=150137
	0x0, 0x2, 0x4a, 0x79,

	// Source Description (offset=32)
	// v=2, p=0, count=1, SDES, len=12
	0x81, 0xca, 0x0, 0xc,
	// ssrc=0x902f9e2e
	0x90, 0x2f, 0x9e, 0x2e,
	// CNAME, len=38
	0x1, 0x26,
	// text="{9c00eb92-1afb-9d49-a47d-91f64eee69f5}"
	0x7b, 0x39, 0x63, 0x30,
	0x30, 0x65, 0x62, 0x39,
	0x32, 0x2d, 0x31, 0x61,
	0x66, 0x62, 0x2d, 0x39,
	0x64, 0x34, 0x39, 0x2d,
	0x61, 0x34, 0x37, 0x64,
	0x2d, 0x39, 0x31, 0x66,
	0x36, 0x34, 0x65, 0x65,
	0x65, 0x36, 0x39, 0x66,
	0x35, 0x7d,
	// END + padding
	0x0, 0x0, 0x0, 0x0,

	// Goodbye (offset=84)
	// v=2, p=0, count=1, BYE, len=1
	0x81, 0xcb, 0x0, 0x1,
	// source=0x902f9e2e
	0x90, 0x2f, 0x9e, 0x2e,

	// Picture Loss Indication (offset=92)
	0x81, 0xce, 0x0, 0x2,
	// sender=0x902f9e2e
	0x90, 0x2f, 0x9e, 0x2e,
	// media=0x902f9e2e
	0x90, 0x2f, 0x9e, 0x2e,

	// RapidResynchronizationRequest (offset=104)
	0x85, 0xcd, 0x0, 0x2,
	// sender=0x902f9e2e
	0x90, 0x2f, 0x9e, 0x2e,
	// media=0x902f9e2e
	0x90, 0x2f, 0x9e, 0x2e,
}

func TestUnmarshal(t *testing.T) {
	decoder := NewDecoder(bytes.NewReader(realPacket))
	packets, err := Unmarshal(realPacket)

	if err != nil {
		t.Fatalf("Error unmarshalling packets: %s", err)
	}

	if len(packets) != 5 {
		t.Fatalf("Read the wrong number of packets from input array")
	}

	// ReceiverReport
	packet := packets[0]
	decoded, err := decoder.DecodePacket()
	assert.NoError(t, err)
	assert.IsType(t, packet, (*ReceiverReport)(nil), "Unmarshalled to incorrect type")

	wantRR := &ReceiverReport{
		SSRC: 0x902f9e2e,
		Reports: []ReceptionReport{{
			SSRC:               0xbc5e9a40,
			FractionLost:       0,
			TotalLost:          0,
			LastSequenceNumber: 0x46e1,
			Jitter:             273,
			LastSenderReport:   0x9f36432,
			Delay:              150137,
		}},
		ProfileExtensions: []byte{},
	}
	if want, got := wantRR, packet; !reflect.DeepEqual(got, want) {
		t.Errorf("Unmarshal rr: got %#v, want %#v", got, want)
	}
	if want, got := wantRR, decoded; !reflect.DeepEqual(got, want) {
		t.Errorf("Unmarshal rr: got %#v, want %#v", got, want)
	}

	// SourceDescription
	packet = packets[1]
	decoded, err = decoder.DecodePacket()
	assert.NoError(t, err)
	assert.IsType(t, packet, (*SourceDescription)(nil), "Unmarshalled to incorrect type")

	wantSdes := &SourceDescription{
		Chunks: []SourceDescriptionChunk{
			{
				Source: 0x902f9e2e,
				Items: []SourceDescriptionItem{
					{
						Type: SDESCNAME,
						Text: "{9c00eb92-1afb-9d49-a47d-91f64eee69f5}",
					},
				},
			},
		},
	}

	if got, want := packet, wantSdes; !reflect.DeepEqual(got, want) {
		t.Errorf("Unmarshal sdes: got %#v, want %#v", got, want)
	}
	if got, want := decoded, wantSdes; !reflect.DeepEqual(got, want) {
		t.Errorf("Unmarshal sdes: got %#v, want %#v", got, want)
	}

	// Goodbye
	packet = packets[2]
	decoded, err = decoder.DecodePacket()
	assert.NoError(t, err)
	assert.IsType(t, packet, (*Goodbye)(nil), "Unmarshalled to incorrect type")

	wantBye := &Goodbye{
		Sources: []uint32{0x902f9e2e},
	}
	if got, want := packet, wantBye; !reflect.DeepEqual(got, want) {
		t.Errorf("Unmarshal bye: got %#v, want %#v", got, want)
	}
	if got, want := decoded, wantBye; !reflect.DeepEqual(got, want) {
		t.Errorf("Unmarshal bye: got %#v, want %#v", got, want)
	}

	// PictureLossIndication
	packet = packets[3]
	decoded, err = decoder.DecodePacket()
	assert.NoError(t, err)
	assert.IsType(t, packet, (*PictureLossIndication)(nil), "Unmarshalled to incorrect type")

	wantPli := &PictureLossIndication{
		SenderSSRC: 0x902f9e2e,
		MediaSSRC:  0x902f9e2e,
	}
	if got, want := packet, wantPli; !reflect.DeepEqual(got, want) {
		t.Errorf("Unmarshal pli: got %#v, want %#v", got, want)
	}
	if got, want := decoded, wantPli; !reflect.DeepEqual(got, want) {
		t.Errorf("Unmarshal pli: got %#v, want %#v", got, want)
	}

	// RapidResynchronizationRequest
	packet = packets[4]
	decoded, err = decoder.DecodePacket()
	assert.NoError(t, err)
	assert.IsType(t, packet, (*RapidResynchronizationRequest)(nil), "Unmarshalled to incorrect type")

	wantRrr := &RapidResynchronizationRequest{
		SenderSSRC: 0x902f9e2e,
		MediaSSRC:  0x902f9e2e,
	}
	if got, want := packet, wantRrr; !reflect.DeepEqual(got, want) {
		t.Errorf("Unmarshal rrr: got %#v, want %#v", got, want)
	}
	if got, want := decoded, wantRrr; !reflect.DeepEqual(got, want) {
		t.Errorf("Unmarshal rrr: got %#v, want %#v", got, want)
	}
}

func TestReadEOF(t *testing.T) {
	shortHeader := []byte{
		0x81, 0xc9, // missing type & len
	}

	_, err := Unmarshal(shortHeader)
	assert.Error(t, err)

	d := NewDecoder(bytes.NewReader(shortHeader))
	_, err = d.DecodePacket()

	assert.Error(t, err)
}

func TestBadCompound(t *testing.T) {
	//trailing data!
	badcompound := realPacket[:34]
	packets, err := Unmarshal(badcompound)
	assert.Error(t, err)

	assert.Nil(t, packets)

	//illegal start -- this should return an error, but also 2 parsed packets
	//it violates the "must start with RR or SR" rule
	badcompound = realPacket[84:104]
	packets, err = Unmarshal(badcompound)
	assert.Error(t, err)
	assert.Equal(t, len(packets), 2)
	assert.Equal(t, packets[0].Header().Type, TypeGoodbye)
	assert.Equal(t, packets[1].Header().Type, TypePayloadSpecificFeedback)
}
<|endoftext|>