<commit_before>package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"os"
	"os/exec"
	"runtime"
	"strconv"

	"github.com/gorilla/websocket"
	"github.com/spf13/cobra"

	"github.com/lxc/lxd/client"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	cli "github.com/lxc/lxd/shared/cmd"
	"github.com/lxc/lxd/shared/i18n"
	"github.com/lxc/lxd/shared/logger"
	"github.com/lxc/lxd/shared/termios"
)

type cmdConsole struct {
	global *cmdGlobal

	flagShowLog bool
	flagType    string
}

func (c *cmdConsole) Command() *cobra.Command {
	cmd := &cobra.Command{}
	cmd.Use = usage("console", i18n.G("[<remote>:]<instance>"))
	cmd.Short = i18n.G("Attach to instance consoles")
	cmd.Long = cli.FormatSection(i18n.G("Description"), i18n.G(
		`Attach to instance consoles

This command allows you to interact with the boot console of an instance
as well as retrieve past log entries from it.`))

	cmd.RunE = c.Run
	cmd.Flags().BoolVar(&c.flagShowLog, "show-log", false, i18n.G("Retrieve the instance's console log"))
	cmd.Flags().StringVarP(&c.flagType, "type", "t", "console", i18n.G("Type of connection to establish: 'console' for serial console, 'vga' for SPICE graphical output")+"``")

	return cmd
}

func (c *cmdConsole) sendTermSize(control *websocket.Conn) error {
	width, height, err := termios.GetSize(int(os.Stdout.Fd()))
	if err != nil {
		return err
	}

	logger.Debugf("Window size is now: %dx%d", width, height)

	msg := api.InstanceExecControl{}
	msg.Command = "window-resize"
	msg.Args = make(map[string]string)
	msg.Args["width"] = strconv.Itoa(width)
	msg.Args["height"] = strconv.Itoa(height)

	return control.WriteJSON(msg)
}

type readWriteCloser struct {
	io.Reader
	io.WriteCloser
}

type stdinMirror struct {
	r                 io.Reader
	consoleDisconnect chan<- bool
	foundEscape       *bool
}

// The pty has been switched to raw mode so we will only ever read a single
// byte. The buffer size is therefore uninteresting to us.
func (er stdinMirror) Read(p []byte) (int, error) {
	n, err := er.r.Read(p)

	v := rune(p[0])
	if v == '\u0001' && !*er.foundEscape {
		*er.foundEscape = true
		return 0, err
	}

	if v == 'q' && *er.foundEscape {
		select {
		case er.consoleDisconnect <- true:
			return 0, err
		default:
			return 0, err
		}
	}

	*er.foundEscape = false
	return n, err
}

func (c *cmdConsole) Run(cmd *cobra.Command, args []string) error {
	conf := c.global.conf

	// Quick checks.
	exit, err := c.global.CheckArgs(cmd, args, 1, 1)
	if exit {
		return err
	}

	// Validate flags.
	if !shared.StringInSlice(c.flagType, []string{"console", "vga"}) {
		return fmt.Errorf("Unknown output type %q", c.flagType)
	}

	// Connect to LXD
	remote, name, err := conf.ParseRemote(args[0])
	if err != nil {
		return err
	}

	d, err := conf.GetInstanceServer(remote)
	if err != nil {
		return err
	}

	// Show the current log if requested
	if c.flagShowLog {
		if c.flagType != "console" {
			return fmt.Errorf("The --show-log flag is only supported for by 'console' output type")
		}

		console := &lxd.InstanceConsoleLogArgs{}
		log, err := d.GetInstanceConsoleLog(name, console)
		if err != nil {
			return err
		}

		stuff, err := ioutil.ReadAll(log)
		if err != nil {
			return err
		}

		fmt.Printf("\n"+i18n.G("Console log:")+"\n\n%s\n", string(stuff))
		return nil
	}

	return c.Console(d, name)
}

func (c *cmdConsole) Console(d lxd.InstanceServer, name string) error {
	if c.flagType == "" {
		c.flagType = "console"
	}
	switch c.flagType {
	case "console":
		return c.console(d, name)
	case "vga":
		return c.vga(d, name)
	}
	return fmt.Errorf("Unknown console type %q", c.flagType)
}

func (c *cmdConsole) console(d lxd.InstanceServer, name string) error {
	// Configure the terminal
	cfd := int(os.Stdin.Fd())

	oldTTYstate, err := termios.MakeRaw(cfd)
	if err != nil {
		return err
	}
	defer termios.Restore(cfd, oldTTYstate)

	handler := c.controlSocketHandler

	var width, height int
	width, height, err = termios.GetSize(int(os.Stdin.Fd()))
	if err != nil {
		return err
	}

	// Prepare the remote console
	req := api.InstanceConsolePost{
		Width:  width,
		Height: height,
		Type:   "console",
	}

	consoleDisconnect := make(chan bool)
	sendDisconnect := make(chan bool)
	defer close(sendDisconnect)

	consoleArgs := lxd.InstanceConsoleArgs{
		Terminal: &readWriteCloser{stdinMirror{os.Stdin,
			sendDisconnect, new(bool)}, os.Stdout},
		Control:           handler,
		ConsoleDisconnect: consoleDisconnect,
	}

	go func() {
		<-sendDisconnect
		close(consoleDisconnect)
	}()

	fmt.Printf(i18n.G("To detach from the console, press: <ctrl>+a q") + "\n\r")

	// Attach to the instance console
	op, err := d.ConsoleInstance(name, req, &consoleArgs)
	if err != nil {
		return err
	}

	// Wait for the operation to complete
	err = op.Wait()
	if err != nil {
		return err
	}

	return nil
}

func (c *cmdConsole) vga(d lxd.InstanceServer, name string) error {
	var err error
	conf := c.global.conf

	// We currently use the control websocket just to abort in case of errors.
	controlDone := make(chan struct{}, 1)
	handler := func(control *websocket.Conn) {
		<-controlDone
		closeMsg := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")
		control.WriteMessage(websocket.CloseMessage, closeMsg)
	}

	// Prepare the remote console.
	req := api.InstanceConsolePost{
		Type: "vga",
	}

	consoleDisconnect := make(chan bool)
	sendDisconnect := make(chan bool)
	defer close(sendDisconnect)

	consoleArgs := lxd.InstanceConsoleArgs{
		Control:           handler,
		ConsoleDisconnect: consoleDisconnect,
	}

	go func() {
		<-sendDisconnect
		close(consoleDisconnect)
	}()

	var socket string
	var listener net.Listener
	if runtime.GOOS != "windows" {
		// Create a temporary unix socket mirroring the instance's spice socket.
		if !shared.PathExists(conf.ConfigPath("sockets")) {
			err := os.MkdirAll(conf.ConfigPath("sockets"), 0700)
			if err != nil {
				return err
			}
		}

		// Generate a random file name.
		path, err := ioutil.TempFile(conf.ConfigPath("sockets"), "*.spice")
		if err != nil {
			return err
		}
		path.Close()

		err = os.Remove(path.Name())
		if err != nil {
			return err
		}

		// Listen on the socket.
		listener, err = net.Listen("unix", path.Name())
		if err != nil {
			return err
		}
		defer listener.Close()
		defer os.Remove(path.Name())

		socket = fmt.Sprintf("spice+unix://%s", path.Name())
	} else {
		listener, err = net.Listen("tcp", "127.0.0.1:0")
		if err != nil {
			return err
		}
		defer listener.Close()

		addr := listener.Addr().(*net.TCPAddr)
		socket = fmt.Sprintf("spice://127.0.0.1:%d", addr.Port)
	}

	op, connect, err := d.ConsoleInstanceDynamic(name, req, &consoleArgs)
	if err != nil {
		return err
	}

	// Handle connections to the socket.
	go func() {
		count := 0

		for {
			conn, err := listener.Accept()
			if err != nil {
				return
			}
			count++

			go func(conn io.ReadWriteCloser) {
				err = connect(conn)
				if err != nil {
					sendDisconnect <- true
				}

				count--
				if count == 0 {
					sendDisconnect <- true
				}
			}(conn)
		}
	}()

	// Use either spicy or remote-viewer if available.
	remoteViewer := c.findCommand("remote-viewer")
	spicy := c.findCommand("spicy")

	if remoteViewer != "" || spicy != "" {
		var cmd *exec.Cmd
		if remoteViewer != "" {
			cmd = exec.Command(remoteViewer, socket)
		} else {
			cmd = exec.Command(spicy, fmt.Sprintf("--uri=%s", socket))
		}

		// Start the command.
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Start()

		defer func() {
			if cmd.Process == nil {
				return
			}

			cmd.Process.Kill()
		}()
	} else {
		fmt.Println(i18n.G("LXD automatically uses either spicy or remote-viewer when present."))
		fmt.Println(i18n.G("As neither could be found, the raw SPICE socket can be found at:"))
		fmt.Printf("  %s\n", socket)
	}

	// Wait for the operation to complete.
	err = op.Wait()
	if err != nil {
		return err
	}

	return nil
}
<commit_msg>lxc/console: Properly handle GUI exitting<commit_after>package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"os"
	"os/exec"
	"runtime"
	"strconv"

	"github.com/gorilla/websocket"
	"github.com/spf13/cobra"

	"github.com/lxc/lxd/client"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	cli "github.com/lxc/lxd/shared/cmd"
	"github.com/lxc/lxd/shared/i18n"
	"github.com/lxc/lxd/shared/logger"
	"github.com/lxc/lxd/shared/termios"
)

type cmdConsole struct {
	global *cmdGlobal

	flagShowLog bool
	flagType    string
}

func (c *cmdConsole) Command() *cobra.Command {
	cmd := &cobra.Command{}
	cmd.Use = usage("console", i18n.G("[<remote>:]<instance>"))
	cmd.Short = i18n.G("Attach to instance consoles")
	cmd.Long = cli.FormatSection(i18n.G("Description"), i18n.G(
		`Attach to instance consoles

This command allows you to interact with the boot console of an instance
as well as retrieve past log entries from it.`))

	cmd.RunE = c.Run
	cmd.Flags().BoolVar(&c.flagShowLog, "show-log", false, i18n.G("Retrieve the instance's console log"))
	cmd.Flags().StringVarP(&c.flagType, "type", "t", "console", i18n.G("Type of connection to establish: 'console' for serial console, 'vga' for SPICE graphical output")+"``")

	return cmd
}

func (c *cmdConsole) sendTermSize(control *websocket.Conn) error {
	width, height, err := termios.GetSize(int(os.Stdout.Fd()))
	if err != nil {
		return err
	}

	logger.Debugf("Window size is now: %dx%d", width, height)

	msg := api.InstanceExecControl{}
	msg.Command = "window-resize"
	msg.Args = make(map[string]string)
	msg.Args["width"] = strconv.Itoa(width)
	msg.Args["height"] = strconv.Itoa(height)

	return control.WriteJSON(msg)
}

type readWriteCloser struct {
	io.Reader
	io.WriteCloser
}

type stdinMirror struct {
	r                 io.Reader
	consoleDisconnect chan<- bool
	foundEscape       *bool
}

// The pty has been switched to raw mode so we will only ever read a single
// byte. The buffer size is therefore uninteresting to us.
func (er stdinMirror) Read(p []byte) (int, error) {
	n, err := er.r.Read(p)

	v := rune(p[0])
	if v == '\u0001' && !*er.foundEscape {
		*er.foundEscape = true
		return 0, err
	}

	if v == 'q' && *er.foundEscape {
		select {
		case er.consoleDisconnect <- true:
			return 0, err
		default:
			return 0, err
		}
	}

	*er.foundEscape = false
	return n, err
}

func (c *cmdConsole) Run(cmd *cobra.Command, args []string) error {
	conf := c.global.conf

	// Quick checks.
	exit, err := c.global.CheckArgs(cmd, args, 1, 1)
	if exit {
		return err
	}

	// Validate flags.
	if !shared.StringInSlice(c.flagType, []string{"console", "vga"}) {
		return fmt.Errorf("Unknown output type %q", c.flagType)
	}

	// Connect to LXD
	remote, name, err := conf.ParseRemote(args[0])
	if err != nil {
		return err
	}

	d, err := conf.GetInstanceServer(remote)
	if err != nil {
		return err
	}

	// Show the current log if requested
	if c.flagShowLog {
		if c.flagType != "console" {
			return fmt.Errorf("The --show-log flag is only supported for by 'console' output type")
		}

		console := &lxd.InstanceConsoleLogArgs{}
		log, err := d.GetInstanceConsoleLog(name, console)
		if err != nil {
			return err
		}

		stuff, err := ioutil.ReadAll(log)
		if err != nil {
			return err
		}

		fmt.Printf("\n"+i18n.G("Console log:")+"\n\n%s\n", string(stuff))
		return nil
	}

	return c.Console(d, name)
}

func (c *cmdConsole) Console(d lxd.InstanceServer, name string) error {
	if c.flagType == "" {
		c.flagType = "console"
	}
	switch c.flagType {
	case "console":
		return c.console(d, name)
	case "vga":
		return c.vga(d, name)
	}
	return fmt.Errorf("Unknown console type %q", c.flagType)
}

func (c *cmdConsole) console(d lxd.InstanceServer, name string) error {
	// Configure the terminal
	cfd := int(os.Stdin.Fd())

	oldTTYstate, err := termios.MakeRaw(cfd)
	if err != nil {
		return err
	}
	defer termios.Restore(cfd, oldTTYstate)

	handler := c.controlSocketHandler

	var width, height int
	width, height, err = termios.GetSize(int(os.Stdin.Fd()))
	if err != nil {
		return err
	}

	// Prepare the remote console
	req := api.InstanceConsolePost{
		Width:  width,
		Height: height,
		Type:   "console",
	}

	consoleDisconnect := make(chan bool)
	sendDisconnect := make(chan bool)
	defer close(sendDisconnect)

	consoleArgs := lxd.InstanceConsoleArgs{
		Terminal: &readWriteCloser{stdinMirror{os.Stdin,
			sendDisconnect, new(bool)}, os.Stdout},
		Control:           handler,
		ConsoleDisconnect: consoleDisconnect,
	}

	go func() {
		<-sendDisconnect
		close(consoleDisconnect)
	}()

	fmt.Printf(i18n.G("To detach from the console, press: <ctrl>+a q") + "\n\r")

	// Attach to the instance console
	op, err := d.ConsoleInstance(name, req, &consoleArgs)
	if err != nil {
		return err
	}

	// Wait for the operation to complete
	err = op.Wait()
	if err != nil {
		return err
	}

	return nil
}

func (c *cmdConsole) vga(d lxd.InstanceServer, name string) error {
	var err error
	conf := c.global.conf

	// We currently use the control websocket just to abort in case of errors.
	controlDone := make(chan struct{}, 1)
	handler := func(control *websocket.Conn) {
		<-controlDone
		closeMsg := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")
		control.WriteMessage(websocket.CloseMessage, closeMsg)
	}

	// Prepare the remote console.
	req := api.InstanceConsolePost{
		Type: "vga",
	}

	consoleDisconnect := make(chan bool)
	sendDisconnect := make(chan bool)
	defer close(sendDisconnect)

	consoleArgs := lxd.InstanceConsoleArgs{
		Control:           handler,
		ConsoleDisconnect: consoleDisconnect,
	}

	go func() {
		<-sendDisconnect
		close(consoleDisconnect)
	}()

	var socket string
	var listener net.Listener
	if runtime.GOOS != "windows" {
		// Create a temporary unix socket mirroring the instance's spice socket.
		if !shared.PathExists(conf.ConfigPath("sockets")) {
			err := os.MkdirAll(conf.ConfigPath("sockets"), 0700)
			if err != nil {
				return err
			}
		}

		// Generate a random file name.
		path, err := ioutil.TempFile(conf.ConfigPath("sockets"), "*.spice")
		if err != nil {
			return err
		}
		path.Close()

		err = os.Remove(path.Name())
		if err != nil {
			return err
		}

		// Listen on the socket.
		listener, err = net.Listen("unix", path.Name())
		if err != nil {
			return err
		}
		defer listener.Close()
		defer os.Remove(path.Name())

		socket = fmt.Sprintf("spice+unix://%s", path.Name())
	} else {
		listener, err = net.Listen("tcp", "127.0.0.1:0")
		if err != nil {
			return err
		}
		defer listener.Close()

		addr := listener.Addr().(*net.TCPAddr)
		socket = fmt.Sprintf("spice://127.0.0.1:%d", addr.Port)
	}

	op, connect, err := d.ConsoleInstanceDynamic(name, req, &consoleArgs)
	if err != nil {
		return err
	}

	// Handle connections to the socket.
	go func() {
		count := 0

		for {
			conn, err := listener.Accept()
			if err != nil {
				return
			}
			count++

			go func(conn io.ReadWriteCloser) {
				err = connect(conn)
				if err != nil {
					sendDisconnect <- true
				}

				count--
				if count == 0 {
					sendDisconnect <- true
				}
			}(conn)
		}
	}()

	// Use either spicy or remote-viewer if available.
	remoteViewer := c.findCommand("remote-viewer")
	spicy := c.findCommand("spicy")

	if remoteViewer != "" || spicy != "" {
		var cmd *exec.Cmd
		if remoteViewer != "" {
			cmd = exec.Command(remoteViewer, socket)
		} else {
			cmd = exec.Command(spicy, fmt.Sprintf("--uri=%s", socket))
		}

		// Start the command.
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Start()

		// Handle the command exitting.
		go func() {
			cmd.Wait()
			sendDisconnect <- true
		}()

		defer func() {
			if cmd.Process == nil {
				return
			}

			cmd.Process.Kill()
		}()
	} else {
		fmt.Println(i18n.G("LXD automatically uses either spicy or remote-viewer when present."))
		fmt.Println(i18n.G("As neither could be found, the raw SPICE socket can be found at:"))
		fmt.Printf("  %s\n", socket)
	}

	// Wait for the operation to complete.
	err = op.Wait()
	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package apps

import (
	"encoding/json"
	"fmt"
	"strings"

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

	"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"
)

type AppUsageEvent struct {
	Entity struct {
		AppName       string `json:"app_name"`
		State         string `json:"state"`
		BuildpackName string `json:"buildpack_name"`
		BuildpackGuid string `json:"buildpack_guid"`
	} `json:"entity"`
}

type AppUsageEvents struct {
	Resources []AppUsageEvent `struct:"resources"`
}

func lastAppUsageEvent(appName string, state string) (bool, AppUsageEvent) {
	var response AppUsageEvents
	cf.AsUser(context.AdminUserContext(), DEFAULT_TIMEOUT, func() {
		cf.ApiRequest("GET", "/v2/app_usage_events?order-direction=desc&page=1", &response, DEFAULT_TIMEOUT)
	})

	for _, event := range response.Resources {
		if event.Entity.AppName == appName && event.Entity.State == state {
			return true, event
		}
	}

	return false, AppUsageEvent{}
}

var _ = Describe("Application Lifecycle", func() {
	var appName string

	BeforeEach(func() {
		appName = generator.RandomName()

		Expect(cf.Cf("push", appName, "-p", assets.NewAssets().Dora).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
	})

	AfterEach(func() {
		Expect(cf.Cf("delete", appName, "-f").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
	})

	Describe("pushing", func() {
		It("makes the app reachable via its bound route", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))
		})

		Describe("Context path", func() {
			var app2 string
			var path = "/imposter_dora"

			BeforeEach(func() {
				app2 = generator.RandomName()
				Expect(cf.Cf("push", app2, "-p", assets.NewAssets().HelloWorld).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
			})

			AfterEach(func() {
				Expect(cf.Cf("delete", app2, "-f").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			})

			It("makes another app available via same host and domain, but different path", func() {
				getRoutePath := fmt.Sprintf("/v2/routes?q=host:%s", appName)
				routeBody := cf.Cf("curl", getRoutePath).Wait(DEFAULT_TIMEOUT).Out.Contents()
				var routeJSON struct {
					Resources []struct {
						Entity struct {
							SpaceGuid  string `json:"space_guid"`
							DomainGuid string `json:"domain_guid"`
						} `json:"entity"`
					} `json:"resources"`
				}
				json.Unmarshal([]byte(routeBody), &routeJSON)

				spaceGuid := routeJSON.Resources[0].Entity.SpaceGuid
				domainGuid := routeJSON.Resources[0].Entity.DomainGuid
				appGuid := cf.Cf("app", app2, "--guid").Wait(DEFAULT_TIMEOUT).Out.Contents()

				jsonBody := "{\"host\":\"" + appName + "\", \"path\":\"" + path + "\", \"domain_guid\":\"" + domainGuid + "\",\"space_guid\":\"" + spaceGuid + "\"}"
				routePostResponseBody := cf.Cf("curl", "/v2/routes", "-X", "POST", "-d", jsonBody).Wait(CF_PUSH_TIMEOUT).Out.Contents()

				var routeResponseJSON struct {
					Metadata struct {
						Guid string `json:"guid"`
					} `json:"metadata"`
				}
				json.Unmarshal([]byte(routePostResponseBody), &routeResponseJSON)
				routeGuid := routeResponseJSON.Metadata.Guid

				Expect(cf.Cf("curl", "/v2/apps/"+strings.TrimSpace(string(appGuid))+"/routes/"+string(routeGuid), "-X", "PUT").Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

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

				Eventually(func() string {
					return helpers.CurlApp(appName, path)
				}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hello, world!"))
			})
		})

		It("makes system environment variables available", func() {
			var envOutput string
			Eventually(func() string {
				envOutput = helpers.CurlApp(appName, "/env")
				return envOutput
			}, DEFAULT_TIMEOUT).Should(ContainSubstring(`"CF_INSTANCE_INDEX"=>"0"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_IP"=>"[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_PORT"=>"[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_ADDR"=>"[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_PORTS"=>"[{\\"external\\":[0-9]+,\\"internal\\":[0-9]+}]"`))
		})

		It("generates an app usage 'started' event", func() {
			found, _ := lastAppUsageEvent(appName, "STARTED")
			Expect(found).To(BeTrue())
		})

		It("generates an app usage 'buildpack_set' event", func() {
			found, matchingEvent := lastAppUsageEvent(appName, "BUILDPACK_SET")

			Expect(found).To(BeTrue())
			Expect(matchingEvent.Entity.BuildpackName).To(Equal("ruby_buildpack"))
			Expect(matchingEvent.Entity.BuildpackGuid).ToNot(BeZero())
		})
	})

	Describe("stopping", func() {
		BeforeEach(func() {
			Expect(cf.Cf("stop", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
		})

		It("makes the app unreachable", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("404"))
		})

		It("generates an app usage 'stopped' event", func() {
			found, _ := lastAppUsageEvent(appName, "STOPPED")
			Expect(found).To(BeTrue())
		})

		Describe("and then starting", func() {
			BeforeEach(func() {
				Expect(cf.Cf("start", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			})

			It("makes the app reachable again", func() {
				Eventually(func() string {
					return helpers.CurlAppRoot(appName)
				}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))
			})
		})
	})

	Describe("updating", func() {
		It("is reflected through another push", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))

			Expect(cf.Cf("push", appName, "-p", assets.NewAssets().HelloWorld).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hello, world!"))
		})
	})

	Describe("deleting", func() {
		BeforeEach(func() {
			Expect(cf.Cf("delete", appName, "-f").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
		})

		It("removes the application", func() {
			app := cf.Cf("app", appName).Wait(DEFAULT_TIMEOUT)
			Expect(app).To(Exit(1))
			Expect(app).To(Say("not found"))
		})

		It("makes the app unreachable", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("404"))
		})

		It("generates an app usage 'stopped' event", func() {
			found, _ := lastAppUsageEvent(appName, "STOPPED")
			Expect(found).To(BeTrue())
		})
	})
})
<commit_msg>Increase page size for app_usage_events so the event we are looking for does not end up on page 2<commit_after>package apps

import (
	"encoding/json"
	"fmt"
	"strings"

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

	"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"
)

type AppUsageEvent struct {
	Entity struct {
		AppName       string `json:"app_name"`
		State         string `json:"state"`
		BuildpackName string `json:"buildpack_name"`
		BuildpackGuid string `json:"buildpack_guid"`
	} `json:"entity"`
}

type AppUsageEvents struct {
	Resources []AppUsageEvent `struct:"resources"`
}

func lastAppUsageEvent(appName string, state string) (bool, AppUsageEvent) {
	var response AppUsageEvents
	cf.AsUser(context.AdminUserContext(), DEFAULT_TIMEOUT, func() {
		cf.ApiRequest("GET", "/v2/app_usage_events?order-direction=desc&page=1&results-per-page=150", &response, DEFAULT_TIMEOUT)
	})

	for _, event := range response.Resources {
		if event.Entity.AppName == appName && event.Entity.State == state {
			return true, event
		}
	}

	return false, AppUsageEvent{}
}

var _ = Describe("Application Lifecycle", func() {
	var appName string

	BeforeEach(func() {
		appName = generator.RandomName()

		Expect(cf.Cf("push", appName, "-p", assets.NewAssets().Dora).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
	})

	AfterEach(func() {
		Expect(cf.Cf("delete", appName, "-f").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
	})

	Describe("pushing", func() {
		It("makes the app reachable via its bound route", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))
		})

		Describe("Context path", func() {
			var app2 string
			var path = "/imposter_dora"

			BeforeEach(func() {
				app2 = generator.RandomName()
				Expect(cf.Cf("push", app2, "-p", assets.NewAssets().HelloWorld).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
			})

			AfterEach(func() {
				Expect(cf.Cf("delete", app2, "-f").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			})

			It("makes another app available via same host and domain, but different path", func() {
				getRoutePath := fmt.Sprintf("/v2/routes?q=host:%s", appName)
				routeBody := cf.Cf("curl", getRoutePath).Wait(DEFAULT_TIMEOUT).Out.Contents()
				var routeJSON struct {
					Resources []struct {
						Entity struct {
							SpaceGuid  string `json:"space_guid"`
							DomainGuid string `json:"domain_guid"`
						} `json:"entity"`
					} `json:"resources"`
				}
				json.Unmarshal([]byte(routeBody), &routeJSON)

				spaceGuid := routeJSON.Resources[0].Entity.SpaceGuid
				domainGuid := routeJSON.Resources[0].Entity.DomainGuid
				appGuid := cf.Cf("app", app2, "--guid").Wait(DEFAULT_TIMEOUT).Out.Contents()

				jsonBody := "{\"host\":\"" + appName + "\", \"path\":\"" + path + "\", \"domain_guid\":\"" + domainGuid + "\",\"space_guid\":\"" + spaceGuid + "\"}"
				routePostResponseBody := cf.Cf("curl", "/v2/routes", "-X", "POST", "-d", jsonBody).Wait(CF_PUSH_TIMEOUT).Out.Contents()

				var routeResponseJSON struct {
					Metadata struct {
						Guid string `json:"guid"`
					} `json:"metadata"`
				}
				json.Unmarshal([]byte(routePostResponseBody), &routeResponseJSON)
				routeGuid := routeResponseJSON.Metadata.Guid

				Expect(cf.Cf("curl", "/v2/apps/"+strings.TrimSpace(string(appGuid))+"/routes/"+string(routeGuid), "-X", "PUT").Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

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

				Eventually(func() string {
					return helpers.CurlApp(appName, path)
				}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hello, world!"))
			})
		})

		It("makes system environment variables available", func() {
			var envOutput string
			Eventually(func() string {
				envOutput = helpers.CurlApp(appName, "/env")
				return envOutput
			}, DEFAULT_TIMEOUT).Should(ContainSubstring(`"CF_INSTANCE_INDEX"=>"0"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_IP"=>"[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_PORT"=>"[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_ADDR"=>"[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_PORTS"=>"[{\\"external\\":[0-9]+,\\"internal\\":[0-9]+}]"`))
		})

		It("generates an app usage 'started' event", func() {
			found, _ := lastAppUsageEvent(appName, "STARTED")
			Expect(found).To(BeTrue())
		})

		It("generates an app usage 'buildpack_set' event", func() {
			found, matchingEvent := lastAppUsageEvent(appName, "BUILDPACK_SET")

			Expect(found).To(BeTrue())
			Expect(matchingEvent.Entity.BuildpackName).To(Equal("ruby_buildpack"))
			Expect(matchingEvent.Entity.BuildpackGuid).ToNot(BeZero())
		})
	})

	Describe("stopping", func() {
		BeforeEach(func() {
			Expect(cf.Cf("stop", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
		})

		It("makes the app unreachable", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("404"))
		})

		It("generates an app usage 'stopped' event", func() {
			found, _ := lastAppUsageEvent(appName, "STOPPED")
			Expect(found).To(BeTrue())
		})

		Describe("and then starting", func() {
			BeforeEach(func() {
				Expect(cf.Cf("start", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			})

			It("makes the app reachable again", func() {
				Eventually(func() string {
					return helpers.CurlAppRoot(appName)
				}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))
			})
		})
	})

	Describe("updating", func() {
		It("is reflected through another push", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))

			Expect(cf.Cf("push", appName, "-p", assets.NewAssets().HelloWorld).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hello, world!"))
		})
	})

	Describe("deleting", func() {
		BeforeEach(func() {
			Expect(cf.Cf("delete", appName, "-f").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
		})

		It("removes the application", func() {
			app := cf.Cf("app", appName).Wait(DEFAULT_TIMEOUT)
			Expect(app).To(Exit(1))
			Expect(app).To(Say("not found"))
		})

		It("makes the app unreachable", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("404"))
		})

		It("generates an app usage 'stopped' event", func() {
			found, _ := lastAppUsageEvent(appName, "STOPPED")
			Expect(found).To(BeTrue())
		})
	})
})
<|endoftext|>


<commit_before>package apps

import (
	"encoding/json"
	"fmt"
	"strings"

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

	"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"
)

type AppUsageEvent struct {
	Entity struct {
		AppName       string `json:"app_name"`
		State         string `json:"state"`
		BuildpackName string `json:"buildpack_name"`
		BuildpackGuid string `json:"buildpack_guid"`
	} `json:"entity"`
}

type AppUsageEvents struct {
	Resources []AppUsageEvent `struct:"resources"`
}

func lastAppUsageEvent(appName string, state string) (bool, AppUsageEvent) {
	var response AppUsageEvents
	cf.AsUser(context.AdminUserContext(), DEFAULT_TIMEOUT, func() {
		cf.ApiRequest("GET", "/v2/app_usage_events?order-direction=desc&page=1", &response, DEFAULT_TIMEOUT)
	})

	for _, event := range response.Resources {
		if event.Entity.AppName == appName && event.Entity.State == state {
			return true, event
		}
	}

	return false, AppUsageEvent{}
}

var _ = Describe("Application Lifecycle", func() {
	var appName string

	BeforeEach(func() {
		appName = generator.RandomName()

		Expect(cf.Cf("push", appName, "-p", assets.NewAssets().Dora).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
	})

	AfterEach(func() {
		Expect(cf.Cf("delete", appName, "-f").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
	})

	Describe("pushing", func() {
		It("makes the app reachable via its bound route", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))
		})

		PDescribe("Context path", func() {
			var app2 string
			var path = "/imposter_dora"

			BeforeEach(func() {
				app2 = generator.RandomName()
				Expect(cf.Cf("push", app2, "-p", assets.NewAssets().HelloWorld).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
			})

			AfterEach(func() {
				Expect(cf.Cf("delete", app2, "-f").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			})

			It("makes another app available via same host and domain, but different path", func() {
				getRoutePath := fmt.Sprintf("/v2/routes?q=host:%s", appName)
				routeBody := cf.Cf("curl", getRoutePath).Wait(DEFAULT_TIMEOUT).Out.Contents()
				var routeJSON struct {
					Resources []struct {
						Entity struct {
							SpaceGuid  string `json:"space_guid"`
							DomainGuid string `json:"domain_guid"`
						} `json:"entity"`
					} `json:"resources"`
				}
				json.Unmarshal([]byte(routeBody), &routeJSON)

				spaceGuid := routeJSON.Resources[0].Entity.SpaceGuid
				domainGuid := routeJSON.Resources[0].Entity.DomainGuid
				appGuid := cf.Cf("app", app2, "--guid").Wait(DEFAULT_TIMEOUT).Out.Contents()

				jsonBody := "{\"host\":\"" + appName + "\", \"path\":\"" + path + "\", \"domain_guid\":\"" + domainGuid + "\",\"space_guid\":\"" + spaceGuid + "\"}"
				routePostResponseBody := cf.Cf("curl", "/v2/routes", "-X", "POST", "-d", jsonBody).Wait(CF_PUSH_TIMEOUT).Out.Contents()

				var routeResponseJSON struct {
					Metadata struct {
						Guid string `json:"guid"`
					} `json:"metadata"`
				}
				json.Unmarshal([]byte(routePostResponseBody), &routeResponseJSON)
				routeGuid := routeResponseJSON.Metadata.Guid

				Expect(cf.Cf("curl", "/v2/apps/"+strings.TrimSpace(string(appGuid))+"/routes/"+string(routeGuid), "-X", "PUT").Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

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

				Eventually(func() string {
					return helpers.CurlApp(appName, path)
				}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hello, world!"))
			})
		})

		It("makes system environment variables available", func() {
			var envOutput string
			Eventually(func() string {
				envOutput = helpers.CurlApp(appName, "/env")
				return envOutput
			}, DEFAULT_TIMEOUT).Should(ContainSubstring(`"CF_INSTANCE_INDEX"=>"0"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_IP"=>"[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_PORT"=>"[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_ADDR"=>"[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_PORTS"=>"[{\\"external\\":[0-9]+,\\"internal\\":[0-9]+}]"`))
		})

		It("generates an app usage 'started' event", func() {
			found, _ := lastAppUsageEvent(appName, "STARTED")
			Expect(found).To(BeTrue())
		})

		It("generates an app usage 'buildpack_set' event", func() {
			found, matchingEvent := lastAppUsageEvent(appName, "BUILDPACK_SET")

			Expect(found).To(BeTrue())
			Expect(matchingEvent.Entity.BuildpackName).To(Equal("ruby_buildpack"))
			Expect(matchingEvent.Entity.BuildpackGuid).ToNot(BeZero())
		})
	})

	Describe("stopping", func() {
		BeforeEach(func() {
			Expect(cf.Cf("stop", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
		})

		It("makes the app unreachable", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("404"))
		})

		It("generates an app usage 'stopped' event", func() {
			found, _ := lastAppUsageEvent(appName, "STOPPED")
			Expect(found).To(BeTrue())
		})

		Describe("and then starting", func() {
			BeforeEach(func() {
				Expect(cf.Cf("start", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			})

			It("makes the app reachable again", func() {
				Eventually(func() string {
					return helpers.CurlAppRoot(appName)
				}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))
			})
		})
	})

	Describe("updating", func() {
		It("is reflected through another push", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))

			Expect(cf.Cf("push", appName, "-p", assets.NewAssets().HelloWorld).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hello, world!"))
		})
	})

	Describe("deleting", func() {
		BeforeEach(func() {
			Expect(cf.Cf("delete", appName, "-f").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
		})

		It("removes the application", func() {
			app := cf.Cf("app", appName).Wait(DEFAULT_TIMEOUT)
			Expect(app).To(Exit(1))
			Expect(app).To(Say("not found"))
		})

		It("makes the app unreachable", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("404"))
		})

		It("generates an app usage 'stopped' event", func() {
			found, _ := lastAppUsageEvent(appName, "STOPPED")
			Expect(found).To(BeTrue())
		})
	})
})
<commit_msg>Unpend context path test<commit_after>package apps

import (
	"encoding/json"
	"fmt"
	"strings"

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

	"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"
)

type AppUsageEvent struct {
	Entity struct {
		AppName       string `json:"app_name"`
		State         string `json:"state"`
		BuildpackName string `json:"buildpack_name"`
		BuildpackGuid string `json:"buildpack_guid"`
	} `json:"entity"`
}

type AppUsageEvents struct {
	Resources []AppUsageEvent `struct:"resources"`
}

func lastAppUsageEvent(appName string, state string) (bool, AppUsageEvent) {
	var response AppUsageEvents
	cf.AsUser(context.AdminUserContext(), DEFAULT_TIMEOUT, func() {
		cf.ApiRequest("GET", "/v2/app_usage_events?order-direction=desc&page=1", &response, DEFAULT_TIMEOUT)
	})

	for _, event := range response.Resources {
		if event.Entity.AppName == appName && event.Entity.State == state {
			return true, event
		}
	}

	return false, AppUsageEvent{}
}

var _ = Describe("Application Lifecycle", func() {
	var appName string

	BeforeEach(func() {
		appName = generator.RandomName()

		Expect(cf.Cf("push", appName, "-p", assets.NewAssets().Dora).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
	})

	AfterEach(func() {
		Expect(cf.Cf("delete", appName, "-f").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
	})

	Describe("pushing", func() {
		It("makes the app reachable via its bound route", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))
		})

		Describe("Context path", func() {
			var app2 string
			var path = "/imposter_dora"

			BeforeEach(func() {
				app2 = generator.RandomName()
				Expect(cf.Cf("push", app2, "-p", assets.NewAssets().HelloWorld).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
			})

			AfterEach(func() {
				Expect(cf.Cf("delete", app2, "-f").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			})

			It("makes another app available via same host and domain, but different path", func() {
				getRoutePath := fmt.Sprintf("/v2/routes?q=host:%s", appName)
				routeBody := cf.Cf("curl", getRoutePath).Wait(DEFAULT_TIMEOUT).Out.Contents()
				var routeJSON struct {
					Resources []struct {
						Entity struct {
							SpaceGuid  string `json:"space_guid"`
							DomainGuid string `json:"domain_guid"`
						} `json:"entity"`
					} `json:"resources"`
				}
				json.Unmarshal([]byte(routeBody), &routeJSON)

				spaceGuid := routeJSON.Resources[0].Entity.SpaceGuid
				domainGuid := routeJSON.Resources[0].Entity.DomainGuid
				appGuid := cf.Cf("app", app2, "--guid").Wait(DEFAULT_TIMEOUT).Out.Contents()

				jsonBody := "{\"host\":\"" + appName + "\", \"path\":\"" + path + "\", \"domain_guid\":\"" + domainGuid + "\",\"space_guid\":\"" + spaceGuid + "\"}"
				routePostResponseBody := cf.Cf("curl", "/v2/routes", "-X", "POST", "-d", jsonBody).Wait(CF_PUSH_TIMEOUT).Out.Contents()

				var routeResponseJSON struct {
					Metadata struct {
						Guid string `json:"guid"`
					} `json:"metadata"`
				}
				json.Unmarshal([]byte(routePostResponseBody), &routeResponseJSON)
				routeGuid := routeResponseJSON.Metadata.Guid

				Expect(cf.Cf("curl", "/v2/apps/"+strings.TrimSpace(string(appGuid))+"/routes/"+string(routeGuid), "-X", "PUT").Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

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

				Eventually(func() string {
					return helpers.CurlApp(appName, path)
				}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hello, world!"))
			})
		})

		It("makes system environment variables available", func() {
			var envOutput string
			Eventually(func() string {
				envOutput = helpers.CurlApp(appName, "/env")
				return envOutput
			}, DEFAULT_TIMEOUT).Should(ContainSubstring(`"CF_INSTANCE_INDEX"=>"0"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_IP"=>"[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_PORT"=>"[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_ADDR"=>"[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_PORTS"=>"[{\\"external\\":[0-9]+,\\"internal\\":[0-9]+}]"`))
		})

		It("generates an app usage 'started' event", func() {
			found, _ := lastAppUsageEvent(appName, "STARTED")
			Expect(found).To(BeTrue())
		})

		It("generates an app usage 'buildpack_set' event", func() {
			found, matchingEvent := lastAppUsageEvent(appName, "BUILDPACK_SET")

			Expect(found).To(BeTrue())
			Expect(matchingEvent.Entity.BuildpackName).To(Equal("ruby_buildpack"))
			Expect(matchingEvent.Entity.BuildpackGuid).ToNot(BeZero())
		})
	})

	Describe("stopping", func() {
		BeforeEach(func() {
			Expect(cf.Cf("stop", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
		})

		It("makes the app unreachable", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("404"))
		})

		It("generates an app usage 'stopped' event", func() {
			found, _ := lastAppUsageEvent(appName, "STOPPED")
			Expect(found).To(BeTrue())
		})

		Describe("and then starting", func() {
			BeforeEach(func() {
				Expect(cf.Cf("start", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			})

			It("makes the app reachable again", func() {
				Eventually(func() string {
					return helpers.CurlAppRoot(appName)
				}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))
			})
		})
	})

	Describe("updating", func() {
		It("is reflected through another push", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))

			Expect(cf.Cf("push", appName, "-p", assets.NewAssets().HelloWorld).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hello, world!"))
		})
	})

	Describe("deleting", func() {
		BeforeEach(func() {
			Expect(cf.Cf("delete", appName, "-f").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
		})

		It("removes the application", func() {
			app := cf.Cf("app", appName).Wait(DEFAULT_TIMEOUT)
			Expect(app).To(Exit(1))
			Expect(app).To(Say("not found"))
		})

		It("makes the app unreachable", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("404"))
		})

		It("generates an app usage 'stopped' event", func() {
			found, _ := lastAppUsageEvent(appName, "STOPPED")
			Expect(found).To(BeTrue())
		})
	})
})
<|endoftext|>


<commit_before>// Copyright 2017 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package client

import (
	"context"
	"errors"

	"github.com/keybase/cli"
	"github.com/keybase/client/go/libcmdline"
	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/protocol/keybase1"
)

type CmdTeamAddMember struct {
	libkb.Contextified
	Team                 string
	Email                string
	Username             string
	Role                 keybase1.TeamRole
	SkipChatNotification bool
}

func newCmdTeamAddMember(cl *libcmdline.CommandLine, g *libkb.GlobalContext) cli.Command {
	return cli.Command{
		Name:         "add-member",
		ArgumentHelp: "<team name> --user=<username> --role=<owner|admin|writer|reader>",
		Usage:        "add a user to a team",
		Action: func(c *cli.Context) {
			cmd := NewCmdTeamAddMemberRunner(g)
			cl.ChooseCommand(cmd, "add-member", c)
		},
		Flags: []cli.Flag{
			cli.StringFlag{
				Name:  "u, user",
				Usage: "username",
			},
			cli.StringFlag{
				Name:  "e, email",
				Usage: "email address to invite",
			},
			cli.StringFlag{
				Name:  "r, role",
				Usage: "team role (owner, admin, writer, reader)",
			},
		},
	}
}

func NewCmdTeamAddMemberRunner(g *libkb.GlobalContext) *CmdTeamAddMember {
	return &CmdTeamAddMember{Contextified: libkb.NewContextified(g)}
}

func (c *CmdTeamAddMember) ParseArgv(ctx *cli.Context) error {
	var err error
	c.Team, err = ParseOneTeamName(ctx)
	if err != nil {
		return err
	}

	c.Email = ctx.String("email")
	if len(c.Email) > 0 {
		if !libkb.CheckEmail.F(c.Email) {
			return errors.New("invalid email address")
		}
		return nil
	}

	c.Username, c.Role, err = ParseUserAndRole(ctx)
	if err != nil {
		return err
	}

	return nil
}

func (c *CmdTeamAddMember) Run() error {
	cli, err := GetTeamsClient(c.G())
	if err != nil {
		return err
	}

	arg := keybase1.TeamAddMemberArg{
		Name:                 c.Team,
		Email:                c.Email,
		Username:             c.Username,
		Role:                 c.Role,
		SendChatNotification: !c.SkipChatNotification,
	}

	res, err := cli.TeamAddMember(context.Background(), arg)
	if err != nil {
		return err
	}

	dui := c.G().UI.GetDumbOutputUI()
	if !res.Invited {
		// TeamAddMember resulted in the user added to the team
		if res.ChatSent {
			dui.Printf("Success! A keybase chat message has been sent to %s.\n", res.User.Username)
		} else {
			dui.Printf("Success! %s added to team.\n", res.User.Username)
		}
		return nil
	}

	// TeamAddMember resulted in the user invited to the team

	if c.Email != "" {
		// email invitation
		dui.Printf("Pending! Email sent to %s with signup instructions. When they join you will be notified.\n", c.Email)
		return nil
	}

	if res.User != nil {
		// user without keys or without puk
		dui.Printf("Pending! Keybase stored a team invitation for %s. When they open the Keybase app, their account will be upgraded and you will be notified.\n", res.User.Username)
	} else {
		// "sharing before signup" user
		dui.Printf("Pending! Keybase stored a team invitation for %s. When they join Keybase you will be notified.\n", c.Username)
	}

	return nil
}

func (c *CmdTeamAddMember) GetUsage() libkb.Usage {
	return libkb.Usage{
		Config:    true,
		API:       true,
		KbKeyring: true,
	}
}
<commit_msg>Fix flag parsing<commit_after>// Copyright 2017 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package client

import (
	"context"
	"errors"

	"github.com/keybase/cli"
	"github.com/keybase/client/go/libcmdline"
	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/protocol/keybase1"
)

type CmdTeamAddMember struct {
	libkb.Contextified
	Team                 string
	Email                string
	Username             string
	Role                 keybase1.TeamRole
	SkipChatNotification bool
}

func newCmdTeamAddMember(cl *libcmdline.CommandLine, g *libkb.GlobalContext) cli.Command {
	return cli.Command{
		Name:         "add-member",
		ArgumentHelp: "<team name> --user=<username> --role=<owner|admin|writer|reader>",
		Usage:        "add a user to a team",
		Action: func(c *cli.Context) {
			cmd := NewCmdTeamAddMemberRunner(g)
			cl.ChooseCommand(cmd, "add-member", c)
		},
		Flags: []cli.Flag{
			cli.StringFlag{
				Name:  "u, user",
				Usage: "username",
			},
			cli.StringFlag{
				Name:  "e, email",
				Usage: "email address to invite",
			},
			cli.StringFlag{
				Name:  "r, role",
				Usage: "team role (owner, admin, writer, reader)",
			},
		},
	}
}

func NewCmdTeamAddMemberRunner(g *libkb.GlobalContext) *CmdTeamAddMember {
	return &CmdTeamAddMember{Contextified: libkb.NewContextified(g)}
}

func (c *CmdTeamAddMember) ParseArgv(ctx *cli.Context) error {
	var err error
	c.Team, err = ParseOneTeamName(ctx)
	if err != nil {
		return err
	}

	c.Role, err = ParseRole(ctx)
	if err != nil {
		return err
	}

	c.Email = ctx.String("email")
	if len(c.Email) > 0 {
		if !libkb.CheckEmail.F(c.Email) {
			return errors.New("invalid email address")
		}
		return nil
	}

	c.Username, err = ParseUser(ctx)
	if err != nil {
		return err
	}

	return nil
}

func (c *CmdTeamAddMember) Run() error {
	cli, err := GetTeamsClient(c.G())
	if err != nil {
		return err
	}

	arg := keybase1.TeamAddMemberArg{
		Name:                 c.Team,
		Email:                c.Email,
		Username:             c.Username,
		Role:                 c.Role,
		SendChatNotification: !c.SkipChatNotification,
	}

	res, err := cli.TeamAddMember(context.Background(), arg)
	if err != nil {
		return err
	}

	dui := c.G().UI.GetDumbOutputUI()
	if !res.Invited {
		// TeamAddMember resulted in the user added to the team
		if res.ChatSent {
			dui.Printf("Success! A keybase chat message has been sent to %s.\n", res.User.Username)
		} else {
			dui.Printf("Success! %s added to team.\n", res.User.Username)
		}
		return nil
	}

	// TeamAddMember resulted in the user invited to the team

	if c.Email != "" {
		// email invitation
		dui.Printf("Pending! Email sent to %s with signup instructions. When they join you will be notified.\n", c.Email)
		return nil
	}

	if res.User != nil {
		// user without keys or without puk
		dui.Printf("Pending! Keybase stored a team invitation for %s. When they open the Keybase app, their account will be upgraded and you will be notified.\n", res.User.Username)
	} else {
		// "sharing before signup" user
		dui.Printf("Pending! Keybase stored a team invitation for %s. When they join Keybase you will be notified.\n", c.Username)
	}

	return nil
}

func (c *CmdTeamAddMember) GetUsage() libkb.Usage {
	return libkb.Usage{
		Config:    true,
		API:       true,
		KbKeyring: true,
	}
}
<|endoftext|>


<commit_before>package dailyemail

import (
	"errors"
	"fmt"
	"socialapi/config"
	"socialapi/workers/email/activityemail/models"
	"socialapi/workers/email/emailmodels"
	"socialapi/workers/helper"
	notificationmodels "socialapi/workers/notification/models"
	"strconv"
	"time"

	"github.com/garyburd/redigo/redis"
	"github.com/koding/logging"
	"github.com/robfig/cron"
)

const (
	DAY           = 24 * time.Hour
	TIMEFORMAT    = "20060102"
	DATEFORMAT    = "Jan 02"
	CACHEPREFIX   = "dailymail"
	RECIPIENTSKEY = "recipients"
	SCHEDULE      = "0 * * * * *"

	Subject = "[Koding] Your Koding Activity for today: %s"
)

type Controller struct {
	log      logging.Logger
	settings *emailmodels.EmailSettings
}

var ObsoleteActivity = errors.New("obsolete activity")

var (
	cronJob *cron.Cron
)

func New(log logging.Logger, es *emailmodels.EmailSettings) (*Controller, error) {

	c := &Controller{
		log:      log,
		settings: es,
	}

	return c, c.initDailyEmailCron()
}

func (n *Controller) initDailyEmailCron() error {
	cronJob = cron.New()
	err := cronJob.AddFunc(SCHEDULE, n.sendDailyMails)
	if err != nil {
		return err
	}
	cronJob.Start()

	return nil
}

func (n *Controller) Shutdown() {
	cronJob.Stop()
}

func (n *Controller) sendDailyMails() {
	redisConn := helper.MustGetRedisConn()
	for {
		key := prepareRecipientsCacheKey()
		reply, err := redisConn.PopSetMember(key)
		if err == redis.ErrNil {
			n.log.Info("all daily mails are sent")
			return
		}
		if err != nil {
			n.log.Error("Could not fetch recipient %s", err)
			return
		}

		accountId, err := strconv.ParseInt(reply, 10, 64)
		if err != nil {
			n.log.Error("Could not cast recipient id: %s", err)
			continue
		}

		if err := n.prepareDailyEmail(accountId); err != nil {
			n.log.Error("error occurred: %s", err)
		}
	}
}

func (n *Controller) prepareDailyEmail(accountId int64) error {
	uc, err := emailmodels.FetchUserContactWithToken(accountId)
	if err != nil {
		return err
	}

	// notifications are disabled
	if val := uc.EmailSettings.Global; !val {
		return nil
	}

	activityIds, err := n.getDailyActivityIds(accountId)
	if err != nil {
		return fmt.Errorf("Could not fetch activity ids: %s", err)
	}

	if len(activityIds) == 0 {
		return nil
	}

	containers := make([]*models.MailerContainer, 0)
	for _, activityId := range activityIds {
		container, err := buildContainerForDailyMail(accountId, activityId)
		if err != nil {
			if err != ObsoleteActivity {
				n.log.Error("error occurred while sending activity: %s ", err)
			}
			continue
		}

		containers = append(containers, container)
	}

	if len(containers) == 0 {
		return nil
	}

	tp := models.NewTemplateParser()
	tp.UserContact = uc
	body, err := tp.RenderDailyTemplate(containers)
	if err != nil {
		return fmt.Errorf("an error occurred while preparing notification email: %s", err)
	}

	mailer := emailmodels.Mailer{
		EmailSettings: n.settings,
		UserContact:   uc,
	}

	subject := fmt.Sprintf(Subject, time.Now().Format(DATEFORMAT))

	return mailer.SendMail("daily", body, subject)
}

func (n *Controller) getDailyActivityIds(accountId int64) ([]int64, error) {
	redisConn := helper.MustGetRedisConn()
	members, err := redisConn.GetSetMembers(prepareDailyActivitiesCacheKey(accountId))
	if err != nil {
		return nil, err
	}

	activityIds := make([]int64, len(members))
	for i, member := range members {
		activityId, err := redisConn.Int64(member)
		if err != nil {
			n.log.Error("Could not get activity id: %s", err)
			continue
		}

		activityIds[i] = activityId
	}

	redisConn.Del(prepareDailyActivitiesCacheKey(accountId))

	return activityIds, nil
}

func prepareRecipientsCacheKey() string {
	return fmt.Sprintf("%s:%s:%s:%s",
		config.MustGet().Environment,
		CACHEPREFIX,
		RECIPIENTSKEY,
		preparePreviousDayCacheKey())
}

func prepareDailyActivitiesCacheKey(accountId int64) string {
	return fmt.Sprintf("%s:%s:%d:%s",
		config.MustGet().Environment,
		CACHEPREFIX,
		accountId,
		preparePreviousDayCacheKey())
}

func preparePreviousDayCacheKey() string {
	return time.Now().Add(-time.Hour * 24).Format(TIMEFORMAT)
}

func buildContainerForDailyMail(accountId, activityId int64) (*models.MailerContainer, error) {
	a := notificationmodels.NewNotificationActivity()
	if err := a.ById(activityId); err != nil {
		return nil, err
	}

	nc, err := a.FetchContent()
	if err != nil {
		return nil, err
	}

	if a.Obsolete && nc.TypeConstant != notificationmodels.NotificationContent_TYPE_COMMENT {
		return nil, ObsoleteActivity
	}

	mc := models.NewMailerContainer()
	mc.AccountId = accountId
	mc.Activity = a
	mc.Content = nc

	if err := mc.PrepareContainer(); err != nil {
		return nil, err
	}

	return mc, nil
}
<commit_msg>email: render daily email date depending on user's last login timezone<commit_after>package dailyemail

import (
	"errors"
	"fmt"
	"socialapi/config"
	"socialapi/workers/email/activityemail/models"
	"socialapi/workers/email/emailmodels"
	"socialapi/workers/helper"
	notificationmodels "socialapi/workers/notification/models"
	"strconv"
	"time"

	"github.com/garyburd/redigo/redis"
	"github.com/koding/logging"
	"github.com/robfig/cron"
)

const (
	DAY           = 24 * time.Hour
	TIMEFORMAT    = "20060102"
	DATEFORMAT    = "Jan 02"
	CACHEPREFIX   = "dailymail"
	RECIPIENTSKEY = "recipients"
	SCHEDULE      = "0 * * * * *"

	Subject = "[Koding] Your Koding Activity for today: %s"
)

type Controller struct {
	log      logging.Logger
	settings *emailmodels.EmailSettings
}

var ObsoleteActivity = errors.New("obsolete activity")

var (
	cronJob *cron.Cron
)

func New(log logging.Logger, es *emailmodels.EmailSettings) (*Controller, error) {

	c := &Controller{
		log:      log,
		settings: es,
	}

	return c, c.initDailyEmailCron()
}

func (n *Controller) initDailyEmailCron() error {
	cronJob = cron.New()
	err := cronJob.AddFunc(SCHEDULE, n.sendDailyMails)
	if err != nil {
		return err
	}
	cronJob.Start()

	return nil
}

func (n *Controller) Shutdown() {
	cronJob.Stop()
}

func (n *Controller) sendDailyMails() {
	redisConn := helper.MustGetRedisConn()
	for {
		key := prepareRecipientsCacheKey()
		reply, err := redisConn.PopSetMember(key)
		if err == redis.ErrNil {
			n.log.Info("all daily mails are sent")
			return
		}
		if err != nil {
			n.log.Error("Could not fetch recipient %s", err)
			return
		}

		accountId, err := strconv.ParseInt(reply, 10, 64)
		if err != nil {
			n.log.Error("Could not cast recipient id: %s", err)
			continue
		}

		if err := n.prepareDailyEmail(accountId); err != nil {
			n.log.Error("error occurred: %s", err)
		}
	}
}

func (n *Controller) prepareDailyEmail(accountId int64) error {
	uc, err := emailmodels.FetchUserContactWithToken(accountId)
	if err != nil {
		return err
	}

	// notifications are disabled
	if val := uc.EmailSettings.Global; !val {
		return nil
	}

	activityIds, err := n.getDailyActivityIds(accountId)
	if err != nil {
		return fmt.Errorf("Could not fetch activity ids: %s", err)
	}

	if len(activityIds) == 0 {
		return nil
	}

	containers := make([]*models.MailerContainer, 0)
	for _, activityId := range activityIds {
		container, err := buildContainerForDailyMail(accountId, activityId)
		if err != nil {
			if err != ObsoleteActivity {
				n.log.Error("error occurred while sending activity: %s ", err)
			}
			continue
		}

		containers = append(containers, container)
	}

	if len(containers) == 0 {
		return nil
	}

	tp := models.NewTemplateParser()
	tp.UserContact = uc
	body, err := tp.RenderDailyTemplate(containers)
	if err != nil {
		return fmt.Errorf("an error occurred while preparing notification email: %s", err)
	}

	mailer := emailmodels.Mailer{
		EmailSettings: n.settings,
		UserContact:   uc,
	}

	var loc *time.Location
	if uc.LastLoginTimezone != "" {
		loc, _ = time.LoadLocation(uc.LastLoginTimezone)
	}

	today := time.Now()
	if loc != nil {
		today = today.In(loc)
	}

	subject := fmt.Sprintf(Subject, today.Format(DATEFORMAT))

	return mailer.SendMail("daily", body, subject)
}

func (n *Controller) getDailyActivityIds(accountId int64) ([]int64, error) {
	redisConn := helper.MustGetRedisConn()
	members, err := redisConn.GetSetMembers(prepareDailyActivitiesCacheKey(accountId))
	if err != nil {
		return nil, err
	}

	activityIds := make([]int64, len(members))
	for i, member := range members {
		activityId, err := redisConn.Int64(member)
		if err != nil {
			n.log.Error("Could not get activity id: %s", err)
			continue
		}

		activityIds[i] = activityId
	}

	redisConn.Del(prepareDailyActivitiesCacheKey(accountId))

	return activityIds, nil
}

func prepareRecipientsCacheKey() string {
	return fmt.Sprintf("%s:%s:%s:%s",
		config.MustGet().Environment,
		CACHEPREFIX,
		RECIPIENTSKEY,
		preparePreviousDayCacheKey())
}

func prepareDailyActivitiesCacheKey(accountId int64) string {
	return fmt.Sprintf("%s:%s:%d:%s",
		config.MustGet().Environment,
		CACHEPREFIX,
		accountId,
		preparePreviousDayCacheKey())
}

func preparePreviousDayCacheKey() string {
	return time.Now().Add(-time.Hour * 24).Format(TIMEFORMAT)
}

func buildContainerForDailyMail(accountId, activityId int64) (*models.MailerContainer, error) {
	a := notificationmodels.NewNotificationActivity()
	if err := a.ById(activityId); err != nil {
		return nil, err
	}

	nc, err := a.FetchContent()
	if err != nil {
		return nil, err
	}

	if a.Obsolete && nc.TypeConstant != notificationmodels.NotificationContent_TYPE_COMMENT {
		return nil, ObsoleteActivity
	}

	mc := models.NewMailerContainer()
	mc.AccountId = accountId
	mc.Activity = a
	mc.Content = nc

	if err := mc.PrepareContainer(); err != nil {
		return nil, err
	}

	return mc, nil
}
<|endoftext|>


<commit_before>package parser
import (
	"errors"
	"strconv"
)

type interpol struct {
	t float64
	u float64
}

func ParseDigitalIn(ch uint8, data []byte) uint8 {
	hex := parseHexDigit(32, data) * 256 + parseHexDigit(33, data)
	if (hex & (1 << ch)) > 0 {
		return 1
	}
	return 0
}

// Sensor type A
func ParseADCSensorA(ch int, data []byte) (ret float64) {
	points := []interpol {
		interpol { 20, 2.47 },
		interpol { 25, 2.37 },
		interpol { 30, 2.27 },
		interpol { 35, 2.17 },
		interpol { 40, 2.06 },
		interpol { 45, 1.94 },
		interpol { 50, 1.82 },
		interpol { 55, 1.70 },
		interpol { 60, 1.57 },
		interpol { 65, 1.46 },
		interpol { 70, 1.34 },
		interpol { 75, 1.23 },
		interpol { 80, 1.13 },
	}
	volt := parseADCVolt(ch, data)
	return interpolate(volt, points)
}

func interpolate(u float64, table []interpol) float64 {

	if u >= table[0].u {
		return table[0].t
	}

	for index := 0; index < len(table); index++ {
		if (table[index].u < u) {
			interval := table[index-1].u - table[index].u
			a := table[index-1].u - u
			frac := a / interval

			t_interval := table[index-1].t - table[index].t

			return table[index-1].t - t_interval * frac
		}
	}

	return table[len(table)-1].t

}

// Sensor type B
func ParseADCSensorB(ch int, data []byte) (ret float64) {
	points := []interpol {
		interpol { -20, 4.54 },
		interpol { -15, 4.42 },
		interpol { -10, 4.29 },
		interpol { -5, 4.13 },
		interpol { 0, 3.96 },
		interpol { 5, 3.77 },
		interpol { 10, 3.56 },
		interpol { 15, 3.34 },
		interpol { 20, 3.05 },
	}
	volt := parseADCVolt(ch, data)
	return interpolate(volt, points)
}

// Sensor type C
func ParseADCSensorC(ch int, data []byte) (ret float64) {
	points := []interpol {
		interpol { 20, 2.60 },
		interpol { 25, 2.47 },
		interpol { 30, 2.34 },
		interpol { 35, 2.20 },
		interpol { 40, 2.06 },
		interpol { 45, 1.91 },
		interpol { 50, 1.77 },
		interpol { 55, 1.63 },
		interpol { 60, 1.49 },
		interpol { 65, 1.36 },
		interpol { 70, 1.23 },
		interpol { 75, 1.12 },
		interpol { 80, 1.01 },
	}
	volt := parseADCVolt(ch, data)
	return interpolate(volt, points)
}

func parseADCVolt(ch int, data []byte) (ret float64) {
	return float64(parseADC(ch, data)) * (4.97 / 1024.0)
}

func parseADC(ch int, data []byte) (ret uint16) {
	index := ch * 4
	ret = parseHexDigit(index, data) * 256
	ret += parseHexDigit(index + 1, data) * 16
	ret += parseHexDigit(index + 2, data)
	return
}

func parseHexDigit(index int, data []byte) uint16 {
	val := uint16(data[index])
	if val > 57 {
		return val - 87
	}
	return val - 48
}

func IsSmallHexDigit(data []byte, index int) error {
	c := data[index]
	if c < 48 || c > 52 {
		return errors.New("char at index " + strconv.Itoa(index) + " must be a valid small hex digit (0-3) but was " + string(c))
	}
	return nil
}

func IsHexDigit(data []byte, index int) error {
	c := data[index]
	if c < 48 || (c > 57 && (c < 97 || c > 102)) {
		return errors.New("char at index " + strconv.Itoa(index) + " must be a valid lowercase hex digit (0-9,a-f) but was " + string(c))
	}
	return nil
}<commit_msg>SERIAL: fix flag processing<commit_after>package parser
import (
	"errors"
	"strconv"
)

type interpol struct {
	t float64
	u float64
}

func ParseDigitalIn(ch uint8, data []byte) uint8 {
	hex := parseHexDigit(32, data) * 16 + parseHexDigit(33, data)
	if (hex & (1 << ch)) > 0 {
		return 1
	}
	return 0
}

// Sensor type A
func ParseADCSensorA(ch int, data []byte) (ret float64) {
	points := []interpol {
		interpol { 20, 2.47 },
		interpol { 25, 2.37 },
		interpol { 30, 2.27 },
		interpol { 35, 2.17 },
		interpol { 40, 2.06 },
		interpol { 45, 1.94 },
		interpol { 50, 1.82 },
		interpol { 55, 1.70 },
		interpol { 60, 1.57 },
		interpol { 65, 1.46 },
		interpol { 70, 1.34 },
		interpol { 75, 1.23 },
		interpol { 80, 1.13 },
	}
	volt := parseADCVolt(ch, data)
	return interpolate(volt, points)
}

func interpolate(u float64, table []interpol) float64 {

	if u >= table[0].u {
		return table[0].t
	}

	for index := 0; index < len(table); index++ {
		if (table[index].u < u) {
			interval := table[index-1].u - table[index].u
			a := table[index-1].u - u
			frac := a / interval

			t_interval := table[index-1].t - table[index].t

			return table[index-1].t - t_interval * frac
		}
	}

	return table[len(table)-1].t

}

// Sensor type B
func ParseADCSensorB(ch int, data []byte) (ret float64) {
	points := []interpol {
		interpol { -20, 4.54 },
		interpol { -15, 4.42 },
		interpol { -10, 4.29 },
		interpol { -5, 4.13 },
		interpol { 0, 3.96 },
		interpol { 5, 3.77 },
		interpol { 10, 3.56 },
		interpol { 15, 3.34 },
		interpol { 20, 3.05 },
	}
	volt := parseADCVolt(ch, data)
	return interpolate(volt, points)
}

// Sensor type C
func ParseADCSensorC(ch int, data []byte) (ret float64) {
	points := []interpol {
		interpol { 20, 2.60 },
		interpol { 25, 2.47 },
		interpol { 30, 2.34 },
		interpol { 35, 2.20 },
		interpol { 40, 2.06 },
		interpol { 45, 1.91 },
		interpol { 50, 1.77 },
		interpol { 55, 1.63 },
		interpol { 60, 1.49 },
		interpol { 65, 1.36 },
		interpol { 70, 1.23 },
		interpol { 75, 1.12 },
		interpol { 80, 1.01 },
	}
	volt := parseADCVolt(ch, data)
	return interpolate(volt, points)
}

func parseADCVolt(ch int, data []byte) (ret float64) {
	return float64(parseADC(ch, data)) * (4.97 / 1024.0)
}

func parseADC(ch int, data []byte) (ret uint16) {
	index := ch * 4
	ret = parseHexDigit(index, data) * 256
	ret += parseHexDigit(index + 1, data) * 16
	ret += parseHexDigit(index + 2, data)
	return
}

func parseHexDigit(index int, data []byte) uint16 {
	val := uint16(data[index])
	if val > 57 {
		return val - 87
	}
	return val - 48
}

func IsSmallHexDigit(data []byte, index int) error {
	c := data[index]
	if c < 48 || c > 52 {
		return errors.New("char at index " + strconv.Itoa(index) + " must be a valid small hex digit (0-3) but was " + string(c))
	}
	return nil
}

func IsHexDigit(data []byte, index int) error {
	c := data[index]
	if c < 48 || (c > 57 && (c < 97 || c > 102)) {
		return errors.New("char at index " + strconv.Itoa(index) + " must be a valid lowercase hex digit (0-9,a-f) but was " + string(c))
	}
	return nil
}
<|endoftext|>


<commit_before>package rbd

/*
#include <errno.h>
*/
import "C"

import (
	"errors"
	"fmt"

	"github.com/ceph/go-ceph/internal/errutil"
)

// revive:disable:exported Temporarily live with stuttering

// RBDError represents an error condition returned from the librbd APIs.
type RBDError int

// revive:enable:exported

func (e RBDError) Error() string {
	errno, s := errutil.FormatErrno(int(e))
	if s == "" {
		return fmt.Sprintf("rbd: ret=%d", errno)
	}
	return fmt.Sprintf("rbd: ret=%d, %s", errno, s)
}

func getError(err C.int) error {
	if err != 0 {
		if err == -C.ENOENT {
			return ErrNotFound
		}
		return RBDError(err)
	} else {
		return nil
	}
}

// Public go errors:

var (
	// ErrNoIOContext may be returned if an api call requires an IOContext and
	// it is not provided.
	ErrNoIOContext = errors.New("RBD image does not have an IOContext")
	// ErrNoName may be returned if an api call requires a name and it is
	// not provided.
	ErrNoName = errors.New("RBD image does not have a name")
	// ErrSnapshotNoName may be returned if an aip call requires a snapshot
	// name and it is not provided.
	ErrSnapshotNoName = errors.New("RBD snapshot does not have a name")
	// ErrImageNotOpen may be returnened if an api call requires an open image handle and one is not provided.
	ErrImageNotOpen = errors.New("RBD image not open")
	// ErrNotFound may be returned from an api call when the requested item is
	// missing.
	ErrNotFound = errors.New("RBD image not found")

	// revive:disable:exported for compatibility with old versions
	RbdErrorImageNotOpen = ErrImageNotOpen
	RbdErrorNotFound     = ErrNotFound
	// revive:enable:exported
)
<commit_msg>rbd: remove a redundant else statement<commit_after>package rbd

/*
#include <errno.h>
*/
import "C"

import (
	"errors"
	"fmt"

	"github.com/ceph/go-ceph/internal/errutil"
)

// revive:disable:exported Temporarily live with stuttering

// RBDError represents an error condition returned from the librbd APIs.
type RBDError int

// revive:enable:exported

func (e RBDError) Error() string {
	errno, s := errutil.FormatErrno(int(e))
	if s == "" {
		return fmt.Sprintf("rbd: ret=%d", errno)
	}
	return fmt.Sprintf("rbd: ret=%d, %s", errno, s)
}

func getError(err C.int) error {
	if err != 0 {
		if err == -C.ENOENT {
			return ErrNotFound
		}
		return RBDError(err)
	}
	return nil
}

// Public go errors:

var (
	// ErrNoIOContext may be returned if an api call requires an IOContext and
	// it is not provided.
	ErrNoIOContext = errors.New("RBD image does not have an IOContext")
	// ErrNoName may be returned if an api call requires a name and it is
	// not provided.
	ErrNoName = errors.New("RBD image does not have a name")
	// ErrSnapshotNoName may be returned if an aip call requires a snapshot
	// name and it is not provided.
	ErrSnapshotNoName = errors.New("RBD snapshot does not have a name")
	// ErrImageNotOpen may be returnened if an api call requires an open image handle and one is not provided.
	ErrImageNotOpen = errors.New("RBD image not open")
	// ErrNotFound may be returned from an api call when the requested item is
	// missing.
	ErrNotFound = errors.New("RBD image not found")

	// revive:disable:exported for compatibility with old versions
	RbdErrorImageNotOpen = ErrImageNotOpen
	RbdErrorNotFound     = ErrNotFound
	// revive:enable:exported
)
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"strconv"

	"github.com/anaminus/but"
	"github.com/anaminus/rbxmk"
	"github.com/anaminus/rbxmk/reflect"
	"github.com/yuin/gopher-lua"
)

// shortenPath transforms the given path so that it is relative to the working
// directory. Returns the original path if that fails.
func shortenPath(filename string) string {
	if wd, err := os.Getwd(); err == nil {
		if abs, err := filepath.Abs(filename); err == nil {
			if r, err := filepath.Rel(wd, abs); err == nil {
				filename = r
			}
		}
	}
	return filename
}

// ParseLuaValue parses a string into a Lua value. Numbers, bools, and nil are
// parsed into their respective types, and any other value is interpreted as a
// string.
func ParseLuaValue(s string) lua.LValue {
	switch s {
	case "true":
		return lua.LTrue
	case "false":
		return lua.LFalse
	case "nil":
		return lua.LNil
	}
	if number, err := strconv.ParseFloat(s, 64); err == nil {
		return lua.LNumber(number)
	}
	return lua.LString(s)
}

const CommandUsage = `rbxmk [ FILE ] [ ...VALUE ]

Receives a file to be executed as a Lua script. If "-" is given, then the script
will be read from stdin instead.

Remaining arguments are Lua values to be passed to the file. Numbers, bools, and
nil are parsed into their respective types in Lua, and any other value is
interpreted as a string.`

func main() {
	// Parse flags.
	flag.Usage = func() {
		fmt.Fprintf(flag.CommandLine.Output(), CommandUsage)
		flag.PrintDefaults()
	}
	flag.Parse()
	args := flag.Args()
	if len(args) == 0 {
		flag.Usage()
		return
	}
	file := args[0]
	args = args[1:]

	// Initialize world.
	state := lua.NewState(lua.Options{SkipOpenLibs: true})
	world := rbxmk.NewWorld(state)
	OpenFilteredLibs(world.State(), GetFilteredStdLib())
	for _, t := range reflect.AllTypes() {
		world.RegisterType(t())
	}

	// Add script arguments.
	for _, arg := range args {
		world.State().Push(ParseLuaValue(arg))
	}

	// Run stdin as script.
	if file == "-" {
		but.IfFatal(world.DoFileHandle(os.Stdin, len(args)))
		return
	}

	// Run file as script.
	filename := shortenPath(filepath.Clean(file))
	but.IfFatal(world.DoFile(filename, len(args)))
}
<commit_msg>Make input and output mockable.<commit_after>package main

import (
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"strconv"

	"github.com/anaminus/but"
	"github.com/anaminus/rbxmk"
	"github.com/anaminus/rbxmk/reflect"
	"github.com/yuin/gopher-lua"
)

// shortenPath transforms the given path so that it is relative to the working
// directory. Returns the original path if that fails.
func shortenPath(filename string) string {
	if wd, err := os.Getwd(); err == nil {
		if abs, err := filepath.Abs(filename); err == nil {
			if r, err := filepath.Rel(wd, abs); err == nil {
				filename = r
			}
		}
	}
	return filename
}

// ParseLuaValue parses a string into a Lua value. Numbers, bools, and nil are
// parsed into their respective types, and any other value is interpreted as a
// string.
func ParseLuaValue(s string) lua.LValue {
	switch s {
	case "true":
		return lua.LTrue
	case "false":
		return lua.LFalse
	case "nil":
		return lua.LNil
	}
	if number, err := strconv.ParseFloat(s, 64); err == nil {
		return lua.LNumber(number)
	}
	return lua.LString(s)
}

type Std struct {
	in  *os.File
	out *os.File
	err *os.File
}

const CommandUsage = `rbxmk [ FILE ] [ ...VALUE ]

Receives a file to be executed as a Lua script. If "-" is given, then the script
will be read from stdin instead.

Remaining arguments are Lua values to be passed to the file. Numbers, bools, and
nil are parsed into their respective types in Lua, and any other value is
interpreted as a string.`

func Main(args []string, std Std) error {
	// Parse flags.
	flagset := flag.NewFlagSet(args[0], flag.ExitOnError)
	flagset.Usage = func() {
		fmt.Fprintf(flagset.Output(), CommandUsage)
		flagset.PrintDefaults()
	}
	flagset.Parse(args[1:])
	args = flagset.Args()
	if len(args) == 0 {
		flagset.Usage()
		return nil
	}
	file := args[0]
	args = args[1:]

	// Initialize world.
	state := lua.NewState(lua.Options{
		SkipOpenLibs:        true,
		IncludeGoStackTrace: false,
	})
	world := rbxmk.NewWorld(state)
	OpenFilteredLibs(world.State(), GetFilteredStdLib())
	for _, t := range reflect.AllTypes() {
		world.RegisterType(t())
	}

	// Add script arguments.
	for _, arg := range args {
		world.State().Push(ParseLuaValue(arg))
	}

	// Run stdin as script.
	if file == "-" {
		return world.DoFileHandle(std.in, len(args))
	}

	// Run file as script.
	filename := shortenPath(filepath.Clean(file))
	return world.DoFile(filename, len(args))
}

func main() {
	but.IfFatal(Main(os.Args, Std{
		in:  os.Stdin,
		out: os.Stdout,
		err: os.Stderr,
	}))
}
<|endoftext|>


<commit_before>package flux

import (
	"errors"
	"fmt"
	"sync"
	"sync/atomic"
)

var (
	//ErrFailedBind represent a failure in binding two Reactors
	ErrFailedBind = errors.New("Failed to Bind Reactors")
)

/*Reactor defines an the idea of continous, reactive change which is a revised implementation of FRP principles with a golang view and approach. Reactor are like a reactive queue where each reactor builds off a previous reactor to allow a simple top-down flow of data.
This approach lends itself from very simple streaming operations to complex stream processing systems.
Due to the use of unbuffered channels, Reactor require that the next keep the rule of the channel contract
.i.e a reactor channel must have someone to collect/listen/retrieve the data within it and
ensure a continouse operation else close and end the reactor
*/

type (

	//Connector defines the core connecting methods used for binding with a Reactor
	Connector interface {
		//Bind provides a convenient way of binding 2 reactors
		Bind(Reactor) error
		//React generates a reactor based off its caller
		React(ReactiveOpHandler) Reactor
	}

	//Replier defines reply methods to reply to requests
	Replier interface {
		//reply functions
		Reply(v interface{})
		ReplyClose(v interface{})
		ReplyError(v error)
	}

	//Sender defines the delivery methods used to deliver data into Reactor process
	Sender interface {
		//delivery functions
		Send(v interface{})
		SendClose(v interface{})
		SendError(v error)
	}

	//SendBinder defines the combination of the Sender and Binding interfaces
	SendBinder interface {
		Sender
		Connector
	}

	//Reactor provides an interface definition for the reactor type to allow compatibility by future extenders when composing with other structs.
	Reactor interface {
		Connector
		Sender
		Replier

		Detach()

		//bool functions for ensuring reactors state
		IsHooked() bool
		HasRoot() bool

		//private functions for swapping in reactors
		UseNext(Reactor) error
		UseRoot(Reactor) error
	}

	//ReactorsView provides a deeper view in the reactor
	ReactorsView interface {
		Reactor
		End()
		Closed() <-chan interface{}
		Signal() <-chan interface{}
		Errors() <-chan error
	}

	//SignalMuxHandler provides a signal function type
	SignalMuxHandler func(d interface{}) interface{}

	//ReactiveOpHandler defines a reactive function operation
	ReactiveOpHandler func(ReactorsView)

	//ReactiveStack provides a concrete implementation
	ReactiveStack struct {
		data, closed      chan interface{}
		errs              chan error
		op                ReactiveOpHandler
		root              Reactor
		next              Reactor
		started, finished int64
		ro, rod           sync.Mutex
	}
)

//DistributeSignals takes from one signal and sends it to other reactors
func DistributeSignals(from Reactor, rs ...Sender) (m Reactor) {
	m = from.React(func(view ReactorsView) {
		defer view.End()
	runloop:
		for {
			select {
			case cd := <-view.Closed():
				for n, rsd := range rs {
					func(data interface{}, ind int, ro Sender) {
						GoDefer(fmt.Sprintf("DeliverClose::to(%d)", ind), func() {
							ro.SendClose(data)
						})
					}(cd, n, rsd)
				}
				break runloop
			case dd := <-view.Signal():
				for n, rsd := range rs {

					func(data interface{}, ind int, ro Sender) {
						GoDefer(fmt.Sprintf("DeliverData::to(%d)", ind), func() {
							ro.Send(data)
						})
					}(dd, n, rsd)

				}
			case de := <-view.Errors():
				for n, rsd := range rs {

					func(data interface{}, ind int, ro Sender) {
						GoDefer(fmt.Sprintf("DeliverError::to(%d)", ind), func() {
							ro.Send(data)
						})
					}(de, n, rsd)

				}
			}
		}
	})

	return
}

//MergeReactors takes input from serveral reactors and turn it into one signal (a []interface{}) signal type
func MergeReactors(rs ...SendBinder) Reactor {
	m := ReactIdentity()

	rdo := new(sync.Mutex)
	maxcount := len(rs) - 1

	for _, rsm := range rs {
		func(ro, col SendBinder) {
			ro.React(func(v ReactorsView) {
			mop:
				for {
					select {
					case err := <-v.Errors():
						m.SendError(err)
					case d := <-v.Closed():
						rdo.Lock()
						if maxcount <= 0 {
							m.SendClose(d)
							break mop
						}
						maxcount--
						rdo.Unlock()
					case d := <-v.Signal():
						m.Send(d)
					}
				}
				v.End()
			})
		}(rsm, m)
	}

	return m
}

//LiftReactors takes inputs from each and pipes it to the next reactor
func LiftReactors(rs ...Reactor) (Reactor, error) {
	if len(rs) < 1 {
		return nil, fmt.Errorf("EmptyArgs: Total Count %d", len(rs))
	}

	if len(rs) == 1 {
		return rs[0], nil
	}

	msl := rs[0]
	rs = rs[1:]

	for _, ro := range rs {
		func(rx Reactor) {
			msl.Bind(rx)
			msl = rx
		}(ro)
	}

	return msl, nil
}

//DataReactWith wraps the whole data react operation
func DataReactWith(mx Connector, fx SignalMuxHandler) Reactor {
	return mx.React(DataReactProcessor(fx))
}

//DataReact returns a reactor that only sends it in to its out
func DataReact(fx SignalMuxHandler) Reactor {
	return Reactive(DataReactProcessor(fx))
}

// ReactIdentity returns a reactor that only sends it in to its out
func ReactIdentity() Reactor {
	return Reactive(ReactIdentityProcessor())
}

//ReactIdentityProcessor provides the ReactIdentity processing op
func ReactIdentityProcessor() ReactiveOpHandler {
	return func(self ReactorsView) {
		func() {
		iloop:
			for {
				select {
				case d := <-self.Closed():
					self.ReplyClose(d)
					break iloop
				case err := <-self.Errors():
					self.ReplyError(err)
				case data := <-self.Signal():
					self.Reply(data)
				}
			}
			self.End()
		}()
	}
}

//DataReactProcessor provides the internal processing ops for DataReact
func DataReactProcessor(fx SignalMuxHandler) ReactiveOpHandler {
	return func(self ReactorsView) {
		func() {
		iloop:
			for {
				select {
				case d := <-self.Closed():
					self.ReplyClose(d)
					break iloop
				case err := <-self.Errors():
					self.ReplyError(err)
				case data := <-self.Signal():
					self.Reply(fx(data))
				}
			}
			self.End()
		}()
	}
}

//Reactive returns a ReactiveStacks,the process is not started immediately if no root exists,to force it,call .ForceRun()
func Reactive(fx ReactiveOpHandler) *ReactiveStack {
	r := &ReactiveStack{
		data:   make(chan interface{}),
		closed: make(chan interface{}),
		errs:   make(chan error),
		op:     fx,
	}

	r.boot()

	return r
}

//Closed returns the error pipe
func (r *ReactiveStack) Closed() <-chan interface{} {
	return r.closed
}

//Errors returns the error pipe
func (r *ReactiveStack) Errors() <-chan error {
	return r.errs
}

// Signal returns the in-put pipe
func (r *ReactiveStack) Signal() <-chan interface{} {
	return r.data
}

//SendError returns the in-put pipe
func (r *ReactiveStack) SendError(d error) {
	state := atomic.LoadInt64(&r.finished)
	if state > 0 {
		return
	}

	if d == nil {
		return
	}

	r.rod.Lock()
	// defer r.rod.Unlock()

	if r.errs == nil {
		return
	}

	r.errs <- d
	r.rod.Unlock()
}

//Send returns the in-put pipe
func (r *ReactiveStack) Send(d interface{}) {
	state := atomic.LoadInt64(&r.finished)
	if state > 0 {
		return
	}

	if d == nil {
		return
	}

	r.rod.Lock()
	// defer r.rod.Unlock()

	if r.data == nil {
		return
	}

	r.data <- d
	r.rod.Unlock()
}

//SendClose returns the in-put pipe
func (r *ReactiveStack) SendClose(d interface{}) {
	state := atomic.LoadInt64(&r.finished)
	if state > 0 {
		return
	}

	if d == nil {
		return
	}

	r.rod.Lock()
	// defer r.rod.Unlock()

	if r.closed == nil {
		return
	}
	r.closed <- d
	r.rod.Unlock()
}

//Reply returns the out-put pipe
func (r *ReactiveStack) Reply(d interface{}) {
	state := atomic.LoadInt64(&r.finished)
	if state > 0 {
		return
	}

	if d == nil {
		return
	}

	if !r.IsHooked() {
		return
	}

	r.next.Send(d)
}

//ReplyClose returns the out-put pipe
func (r *ReactiveStack) ReplyClose(d interface{}) {
	state := atomic.LoadInt64(&r.finished)
	if state > 0 {
		return
	}

	if d == nil {
		return
	}

	if !r.IsHooked() {
		return
	}

	r.next.SendClose(d)
}

//ReplyError returns the out-put pipe
func (r *ReactiveStack) ReplyError(d error) {
	state := atomic.LoadInt64(&r.finished)
	if state > 0 {
		return
	}

	if d == nil {
		return
	}

	if !r.IsHooked() {
		return
	}

	r.next.SendError(d)
}

// HasRoot returns true/false if its has a chain
func (r *ReactiveStack) HasRoot() bool {
	return r.root != nil
}

// IsHooked returns true/false if its has a chain
func (r *ReactiveStack) IsHooked() bool {
	r.ro.Lock()
	state := (r.next != nil)
	r.ro.Unlock()
	return state
}

//Bind connects a reactor to the next available reactor in the chain that has no binding,you can only bind if the provided reactor has no binding (root) and if the target reactor has no next. A bool value is returned to indicate success or failure
func (r *ReactiveStack) Bind(fx Reactor) error {
	if err := r.UseNext(fx); err != nil {
		return r.next.Bind(fx)
	}

	fx.UseRoot(r)
	return nil
}

//React creates a reactivestack from this current one
func (r *ReactiveStack) React(fx ReactiveOpHandler) Reactor {

	if r.next != nil {
		return r.next.React(fx)
	}

	nx := Reactive(fx)
	nx.root = r

	r.next = nx

	return r.next
}

//End signals to the next stack its closing
func (r *ReactiveStack) End() {
	state := atomic.LoadInt64(&r.finished)
	if state > 0 {
		return
	}

	atomic.StoreInt64(&r.finished, 1)

	if r.root != nil {
		r.root.Detach()
		r.root = nil
	}
}

//UseRoot allows setting the root Reactor if there is non set
func (r *ReactiveStack) UseRoot(fx Reactor) error {
	if r.root != nil {
		return ErrFailedBind
	}
	r.root = fx
	return ErrFailedBind
}

//UseNext allows setting the next Reactor if there is non set
func (r *ReactiveStack) UseNext(fx Reactor) error {
	if r.next != nil {
		return ErrFailedBind
	}

	r.next = fx
	return nil
}

//Detach nullifies the next link of this Reactor
func (r *ReactiveStack) Detach() {
	r.ro.Lock()
	r.next = nil
	r.ro.Unlock()
}

//ForceRun forces the immediate start of the reactor
func (r *ReactiveStack) boot() {
	//bootup this reactor
	if r.started > 0 {
		return
	}

	atomic.StoreInt64(&r.started, 1)
	GoDefer("StartReact", func() {
		r.op(r)
	})
}

type (
	//ChannelStream provides a simple struct for exposing outputs from Reactor to outside
	ChannelStream struct {
		Close  chan interface{}
		Data   chan interface{}
		Errors chan error
	}
)

//NewChannelStream returns a new channel stream instance
func NewChannelStream() *ChannelStream {
	return &ChannelStream{
		Close:  make(chan interface{}),
		Data:   make(chan interface{}),
		Errors: make(chan error),
	}
}

//ChannelReact builds a ChannelStream directly with a Reactor
func ChannelReact(c *ChannelStream) Reactor {
	return Reactive(ChannelProcessor(c))
}

//ChannelReactWith provides a factory to create a Reactor to a channel
func ChannelReactWith(mx Connector, c *ChannelStream) Reactor {
	return mx.React(ChannelProcessor(c))
}

//ChannelProcessor provides the ReactIdentity processing op
func ChannelProcessor(c *ChannelStream) ReactiveOpHandler {
	return func(self ReactorsView) {
		func() {
		iloop:
			for {
				select {
				case d := <-self.Closed():
					GoDefer("ChannelClose", func() {
						c.Close <- d
					})
					self.ReplyClose(d)
					break iloop
				case err := <-self.Errors():
					GoDefer("ChannelError", func() {
						c.Errors <- err
					})
					self.ReplyError(err)
				case data := <-self.Signal():
					GoDefer("ChannelData", func() {
						c.Data <- data
					})
					self.Reply(data)
				}
			}
			self.End()
		}()
	}
}
<commit_msg>adding composition fix: added err check fix<commit_after>package flux

import (
	"errors"
	"fmt"
	"sync"
	"sync/atomic"
)

var (
	//ErrFailedBind represent a failure in binding two Reactors
	ErrFailedBind = errors.New("Failed to Bind Reactors")
)

/*Reactor defines an the idea of continous, reactive change which is a revised implementation of FRP principles with a golang view and approach. Reactor are like a reactive queue where each reactor builds off a previous reactor to allow a simple top-down flow of data.
This approach lends itself from very simple streaming operations to complex stream processing systems.
Due to the use of unbuffered channels, Reactor require that the next keep the rule of the channel contract
.i.e a reactor channel must have someone to collect/listen/retrieve the data within it and
ensure a continouse operation else close and end the reactor
*/

type (

	//Connector defines the core connecting methods used for binding with a Reactor
	Connector interface {
		//Bind provides a convenient way of binding 2 reactors
		Bind(Reactor) error
		//React generates a reactor based off its caller
		React(ReactiveOpHandler) Reactor
	}

	//Replier defines reply methods to reply to requests
	Replier interface {
		//reply functions
		Reply(v interface{})
		ReplyClose(v interface{})
		ReplyError(v error)
	}

	//Sender defines the delivery methods used to deliver data into Reactor process
	Sender interface {
		//delivery functions
		Send(v interface{})
		SendClose(v interface{})
		SendError(v error)
	}

	//SendBinder defines the combination of the Sender and Binding interfaces
	SendBinder interface {
		Sender
		Connector
	}

	//Reactor provides an interface definition for the reactor type to allow compatibility by future extenders when composing with other structs.
	Reactor interface {
		Connector
		Sender
		Replier

		Detach()

		//bool functions for ensuring reactors state
		IsHooked() bool
		HasRoot() bool

		//private functions for swapping in reactors
		UseNext(Reactor) error
		UseRoot(Reactor) error
	}

	//ReactorsView provides a deeper view in the reactor
	ReactorsView interface {
		Reactor
		End()
		Closed() <-chan interface{}
		Signal() <-chan interface{}
		Errors() <-chan error
	}

	//SignalMuxHandler provides a signal function type
	SignalMuxHandler func(d interface{}) interface{}

	//ReactiveOpHandler defines a reactive function operation
	ReactiveOpHandler func(ReactorsView)

	//ReactiveStack provides a concrete implementation
	ReactiveStack struct {
		data, closed      chan interface{}
		errs              chan error
		op                ReactiveOpHandler
		root              Reactor
		next              Reactor
		started, finished int64
		ro, rod           sync.Mutex
	}
)

//DistributeSignals takes from one signal and sends it to other reactors
func DistributeSignals(from Reactor, rs ...Sender) (m Reactor) {
	m = from.React(func(view ReactorsView) {
		defer view.End()
	runloop:
		for {
			select {
			case cd := <-view.Closed():
				for n, rsd := range rs {
					func(data interface{}, ind int, ro Sender) {
						GoDefer(fmt.Sprintf("DeliverClose::to(%d)", ind), func() {
							ro.SendClose(data)
						})
					}(cd, n, rsd)
				}
				break runloop
			case dd := <-view.Signal():
				for n, rsd := range rs {

					func(data interface{}, ind int, ro Sender) {
						GoDefer(fmt.Sprintf("DeliverData::to(%d)", ind), func() {
							ro.Send(data)
						})
					}(dd, n, rsd)

				}
			case de := <-view.Errors():
				for n, rsd := range rs {

					func(data interface{}, ind int, ro Sender) {
						GoDefer(fmt.Sprintf("DeliverError::to(%d)", ind), func() {
							ro.Send(data)
						})
					}(de, n, rsd)

				}
			}
		}
	})

	return
}

//MergeReactors takes input from serveral reactors and turn it into one signal (a []interface{}) signal type
func MergeReactors(rs ...SendBinder) Reactor {
	m := ReactIdentity()

	rdo := new(sync.Mutex)
	maxcount := len(rs) - 1

	for _, rsm := range rs {
		func(ro, col SendBinder) {
			ro.React(func(v ReactorsView) {
			mop:
				for {
					select {
					case err := <-v.Errors():
						m.SendError(err)
					case d := <-v.Closed():
						rdo.Lock()
						if maxcount <= 0 {
							m.SendClose(d)
							break mop
						}
						maxcount--
						rdo.Unlock()
					case d := <-v.Signal():
						m.Send(d)
					}
				}
				v.End()
			})
		}(rsm, m)
	}

	return m
}

//LiftReactors takes inputs from each and pipes it to the next reactor
func LiftReactors(rs ...Reactor) (Reactor, error) {
	if len(rs) < 1 {
		return nil, fmt.Errorf("EmptyArgs: Total Count %d", len(rs))
	}

	if len(rs) == 1 {
		return rs[0], nil
	}

	msl := rs[0]
	rs = rs[1:]

	for _, ro := range rs {
		func(rx Reactor) {
			if err := msl.Bind(rx); err == nil {
				msl = rx
			}
		}(ro)
	}

	return msl, nil
}

//DataReactWith wraps the whole data react operation
func DataReactWith(mx Connector, fx SignalMuxHandler) Reactor {
	return mx.React(DataReactProcessor(fx))
}

//DataReact returns a reactor that only sends it in to its out
func DataReact(fx SignalMuxHandler) Reactor {
	return Reactive(DataReactProcessor(fx))
}

// ReactIdentity returns a reactor that only sends it in to its out
func ReactIdentity() Reactor {
	return Reactive(ReactIdentityProcessor())
}

//ReactIdentityProcessor provides the ReactIdentity processing op
func ReactIdentityProcessor() ReactiveOpHandler {
	return func(self ReactorsView) {
		func() {
		iloop:
			for {
				select {
				case d := <-self.Closed():
					self.ReplyClose(d)
					break iloop
				case err := <-self.Errors():
					self.ReplyError(err)
				case data := <-self.Signal():
					self.Reply(data)
				}
			}
			self.End()
		}()
	}
}

//DataReactProcessor provides the internal processing ops for DataReact
func DataReactProcessor(fx SignalMuxHandler) ReactiveOpHandler {
	return func(self ReactorsView) {
		func() {
		iloop:
			for {
				select {
				case d := <-self.Closed():
					self.ReplyClose(d)
					break iloop
				case err := <-self.Errors():
					self.ReplyError(err)
				case data := <-self.Signal():
					self.Reply(fx(data))
				}
			}
			self.End()
		}()
	}
}

//Reactive returns a ReactiveStacks,the process is not started immediately if no root exists,to force it,call .ForceRun()
func Reactive(fx ReactiveOpHandler) *ReactiveStack {
	r := &ReactiveStack{
		data:   make(chan interface{}),
		closed: make(chan interface{}),
		errs:   make(chan error),
		op:     fx,
	}

	r.boot()

	return r
}

//Closed returns the error pipe
func (r *ReactiveStack) Closed() <-chan interface{} {
	return r.closed
}

//Errors returns the error pipe
func (r *ReactiveStack) Errors() <-chan error {
	return r.errs
}

// Signal returns the in-put pipe
func (r *ReactiveStack) Signal() <-chan interface{} {
	return r.data
}

//SendError returns the in-put pipe
func (r *ReactiveStack) SendError(d error) {
	state := atomic.LoadInt64(&r.finished)
	if state > 0 {
		return
	}

	if d == nil {
		return
	}

	r.rod.Lock()
	// defer r.rod.Unlock()

	if r.errs == nil {
		return
	}

	r.errs <- d
	r.rod.Unlock()
}

//Send returns the in-put pipe
func (r *ReactiveStack) Send(d interface{}) {
	state := atomic.LoadInt64(&r.finished)
	if state > 0 {
		return
	}

	if d == nil {
		return
	}

	r.rod.Lock()
	// defer r.rod.Unlock()

	if r.data == nil {
		return
	}

	r.data <- d
	r.rod.Unlock()
}

//SendClose returns the in-put pipe
func (r *ReactiveStack) SendClose(d interface{}) {
	state := atomic.LoadInt64(&r.finished)
	if state > 0 {
		return
	}

	if d == nil {
		return
	}

	r.rod.Lock()
	// defer r.rod.Unlock()

	if r.closed == nil {
		return
	}
	r.closed <- d
	r.rod.Unlock()
}

//Reply returns the out-put pipe
func (r *ReactiveStack) Reply(d interface{}) {
	state := atomic.LoadInt64(&r.finished)
	if state > 0 {
		return
	}

	if d == nil {
		return
	}

	if !r.IsHooked() {
		return
	}

	r.next.Send(d)
}

//ReplyClose returns the out-put pipe
func (r *ReactiveStack) ReplyClose(d interface{}) {
	state := atomic.LoadInt64(&r.finished)
	if state > 0 {
		return
	}

	if d == nil {
		return
	}

	if !r.IsHooked() {
		return
	}

	r.next.SendClose(d)
}

//ReplyError returns the out-put pipe
func (r *ReactiveStack) ReplyError(d error) {
	state := atomic.LoadInt64(&r.finished)
	if state > 0 {
		return
	}

	if d == nil {
		return
	}

	if !r.IsHooked() {
		return
	}

	r.next.SendError(d)
}

// HasRoot returns true/false if its has a chain
func (r *ReactiveStack) HasRoot() bool {
	return r.root != nil
}

// IsHooked returns true/false if its has a chain
func (r *ReactiveStack) IsHooked() bool {
	r.ro.Lock()
	state := (r.next != nil)
	r.ro.Unlock()
	return state
}

//Bind connects a reactor to the next available reactor in the chain that has no binding,you can only bind if the provided reactor has no binding (root) and if the target reactor has no next. A bool value is returned to indicate success or failure
func (r *ReactiveStack) Bind(fx Reactor) error {
	if err := r.UseNext(fx); err != nil {
		return r.next.Bind(fx)
	}

	fx.UseRoot(r)
	return nil
}

//React creates a reactivestack from this current one
func (r *ReactiveStack) React(fx ReactiveOpHandler) Reactor {

	if r.next != nil {
		return r.next.React(fx)
	}

	nx := Reactive(fx)
	nx.root = r

	r.next = nx

	return r.next
}

//End signals to the next stack its closing
func (r *ReactiveStack) End() {
	state := atomic.LoadInt64(&r.finished)
	if state > 0 {
		return
	}

	atomic.StoreInt64(&r.finished, 1)

	if r.root != nil {
		r.root.Detach()
		r.root = nil
	}
}

//UseRoot allows setting the root Reactor if there is non set
func (r *ReactiveStack) UseRoot(fx Reactor) error {
	if r.root != nil {
		return ErrFailedBind
	}
	r.root = fx
	return ErrFailedBind
}

//UseNext allows setting the next Reactor if there is non set
func (r *ReactiveStack) UseNext(fx Reactor) error {
	if r.next != nil {
		return ErrFailedBind
	}

	r.next = fx
	return nil
}

//Detach nullifies the next link of this Reactor
func (r *ReactiveStack) Detach() {
	r.ro.Lock()
	r.next = nil
	r.ro.Unlock()
}

//ForceRun forces the immediate start of the reactor
func (r *ReactiveStack) boot() {
	//bootup this reactor
	if r.started > 0 {
		return
	}

	atomic.StoreInt64(&r.started, 1)
	GoDefer("StartReact", func() {
		r.op(r)
	})
}

type (
	//ChannelStream provides a simple struct for exposing outputs from Reactor to outside
	ChannelStream struct {
		Close  chan interface{}
		Data   chan interface{}
		Errors chan error
	}
)

//NewChannelStream returns a new channel stream instance
func NewChannelStream() *ChannelStream {
	return &ChannelStream{
		Close:  make(chan interface{}),
		Data:   make(chan interface{}),
		Errors: make(chan error),
	}
}

//ChannelReact builds a ChannelStream directly with a Reactor
func ChannelReact(c *ChannelStream) Reactor {
	return Reactive(ChannelProcessor(c))
}

//ChannelReactWith provides a factory to create a Reactor to a channel
func ChannelReactWith(mx Connector, c *ChannelStream) Reactor {
	return mx.React(ChannelProcessor(c))
}

//ChannelProcessor provides the ReactIdentity processing op
func ChannelProcessor(c *ChannelStream) ReactiveOpHandler {
	return func(self ReactorsView) {
		func() {
		iloop:
			for {
				select {
				case d := <-self.Closed():
					GoDefer("ChannelClose", func() {
						c.Close <- d
					})
					self.ReplyClose(d)
					break iloop
				case err := <-self.Errors():
					GoDefer("ChannelError", func() {
						c.Errors <- err
					})
					self.ReplyError(err)
				case data := <-self.Signal():
					GoDefer("ChannelData", func() {
						c.Data <- data
					})
					self.Reply(data)
				}
			}
			self.End()
		}()
	}
}
<|endoftext|>