<commit_before>package gqt_test

import (
	"bytes"
	"io"
	"os/exec"
	"path/filepath"
	"strings"

	"code.cloudfoundry.org/garden"
	"code.cloudfoundry.org/guardian/gqt/runner"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/gexec"
)

var _ = Describe("Containerd", func() {
	var client *runner.RunningGarden

	BeforeEach(func() {
		skipIfNotContainerd()
	})

	JustBeforeEach(func() {
		client = runner.Start(config)
	})

	AfterEach(func() {
		Expect(client.DestroyAndStop()).To(Succeed())
	})

	Describe("creating containers", func() {
		It("creates a containerd container with running init task", func() {
			container, err := client.Create(garden.ContainerSpec{})
			Expect(err).NotTo(HaveOccurred())

			containers := listContainers("ctr", config.ContainerdSocket)
			Expect(containers).To(ContainSubstring(container.Handle()))

			tasks := listTasks("ctr", config.ContainerdSocket)
			Expect(tasks).To(ContainSubstring(container.Handle()))
			Expect(tasks).To(MatchRegexp(container.Handle() + `\s+\d+\s+RUNNING`))
		})
	})

	Describe("destroying a container", func() {
		var container garden.Container

		JustBeforeEach(func() {
			var err error
			container, err = client.Create(garden.ContainerSpec{})
			Expect(err).NotTo(HaveOccurred())
		})

		It("removes the container from ctr lookup", func() {
			err := client.Destroy(container.Handle())
			Expect(err).NotTo(HaveOccurred())

			containers := listContainers("ctr", config.ContainerdSocket)
			Expect(containers).NotTo(ContainSubstring(container.Handle()))
		})
	})

	Describe("running a process in a container", func() {
		var (
			processID string
			container garden.Container
		)

		JustBeforeEach(func() {
			var err error
			container, err = client.Create(garden.ContainerSpec{})
			Expect(err).NotTo(HaveOccurred())
		})

		AfterEach(func() {
			Expect(client.Destroy(container.Handle())).To(Succeed())
		})

		It("succeeds", func() {
			process, err := container.Run(garden.ProcessSpec{
				Path: "/bin/sh",
				Args: []string{"-c", "exit 17"},
			}, garden.ProcessIO{})
			Expect(err).NotTo(HaveOccurred())
			statusCode, err := process.Wait()
			Expect(err).NotTo(HaveOccurred())
			Expect(statusCode).To(Equal(17))
		})

		It("can attach to a process", func() {
			process, err := container.Run(garden.ProcessSpec{
				Path: "/bin/sh",
				Args: []string{"-c", "exit 13"},
			}, garden.ProcessIO{})
			Expect(err).NotTo(HaveOccurred())
			processID = process.ID()

			attachedProcess, err := container.Attach(processID, garden.ProcessIO{})
			Expect(err).NotTo(HaveOccurred())

			exitCode, err := attachedProcess.Wait()
			Expect(err).NotTo(HaveOccurred())
			Expect(exitCode).To(Equal(13))
		})

		Context("when use_containerd_for_processes is enabled", func() {
			BeforeEach(func() {
				config.UseContainerdForProcesses = boolptr(true)
			})

			It("is known about by containerd", func() {
				_, err := container.Run(garden.ProcessSpec{
					ID:   "ctrd-process-id",
					Path: "/bin/sleep",
					Args: []string{"10"},
					Dir:  "/",
				}, garden.ProcessIO{})
				Expect(err).NotTo(HaveOccurred())

				processes := listProcesses("ctr", config.ContainerdSocket, container.Handle())
				Expect(processes).To(ContainSubstring("ctrd-process-id"))
			})

			It("can resolve the user of the process", func() {
				stdout := gbytes.NewBuffer()
				_, err := container.Run(garden.ProcessSpec{
					ID:   "ctrd-process-id",
					Path: "/bin/ps",
					User: "1000",
					Dir:  "/",
				}, garden.ProcessIO{
					Stdout: io.MultiWriter(GinkgoWriter, stdout),
				})
				Expect(err).NotTo(HaveOccurred())
				Eventually(stdout).Should(gbytes.Say("alice"))
			})

			It("can resolve the home directory of the user if none was specified", func() {
				stdout := gbytes.NewBuffer()
				_, err := container.Run(garden.ProcessSpec{
					ID:   "ctrd-process-pwd",
					Path: "/bin/pwd",
					User: "alice",
				}, garden.ProcessIO{
					Stdout: io.MultiWriter(GinkgoWriter, stdout),
				})
				Expect(err).NotTo(HaveOccurred())

				Eventually(stdout).Should(gbytes.Say("/home/alice"))
			})

			It("can run a process without providing an ID", func() {
				stdout := gbytes.NewBuffer()
				_, err := container.Run(garden.ProcessSpec{
					Path: "/bin/echo",
					Args: []string{"hello alice"},
				}, garden.ProcessIO{
					Stdout: io.MultiWriter(GinkgoWriter, stdout),
				})
				Expect(err).NotTo(HaveOccurred())

				Eventually(stdout).Should(gbytes.Say("hello alice"))
			})

			It("can get the exit code of a process", func() {
				process, err := container.Run(garden.ProcessSpec{
					Path: "/bin/sh",
					Args: []string{"-c", "exit 17"},
				}, garden.ProcessIO{})
				Expect(err).NotTo(HaveOccurred())

				exitCode, err := process.Wait()
				Expect(err).NotTo(HaveOccurred())

				Expect(exitCode).To(Equal(17))
			})

			Describe("Stdio", func() {
				It("connects stdin", func() {
					stdout := gbytes.NewBuffer()
					stdin := bytes.NewBufferString("hello from stdin")
					process, err := container.Run(garden.ProcessSpec{
						Path: "cat",
					}, garden.ProcessIO{
						Stdin:  stdin,
						Stdout: io.MultiWriter(GinkgoWriter, stdout),
					})
					Expect(err).NotTo(HaveOccurred())
					Expect(process.Wait()).To(Equal(0))

					Eventually(stdout).Should(gbytes.Say("hello from stdin"))
				})

				It("connects stdout", func() {
					stdout := new(bytes.Buffer)
					process, err := container.Run(garden.ProcessSpec{
						Path: "/bin/echo",
						Args: []string{"-n", "hello world"},
					}, garden.ProcessIO{
						Stdout: io.MultiWriter(GinkgoWriter, stdout),
					})
					Expect(err).NotTo(HaveOccurred())
					Expect(process.Wait()).To(Equal(0))

					Expect(stdout.String()).To(Equal("hello world"))
				})

				It("connects stderr", func() {
					stderr := new(bytes.Buffer)
					process, err := container.Run(garden.ProcessSpec{
						Path: "/bin/sh",
						Args: []string{"-c", "/bin/echo -n hello error 1>&2"},
					}, garden.ProcessIO{
						Stderr: io.MultiWriter(GinkgoWriter, stderr),
					})
					Expect(err).NotTo(HaveOccurred())
					Expect(process.Wait()).To(Equal(0))

					Expect(stderr.String()).To(Equal("hello error"))
				})
			})

			Describe("Signalling", func() {
				It("forwards signals to the process", func() {
					buffer := gbytes.NewBuffer()
					proc, err := container.Run(garden.ProcessSpec{
						Path: "sh",
						Args: []string{"-c", `
					trap 'exit 42' TERM

					while true; do
					  echo 'sleeping'
					  sleep 1
					done
				`},
					}, garden.ProcessIO{
						Stdout: buffer,
					})
					Expect(err).NotTo(HaveOccurred())

					Eventually(buffer).Should(gbytes.Say("sleeping"))

					err = proc.Signal(garden.SignalTerminate)
					Expect(err).NotTo(HaveOccurred())

					status := make(chan int)
					go func() {
						exit, err := proc.Wait()
						Expect(err).NotTo(HaveOccurred())
						status <- exit
					}()

					Eventually(status).Should(Receive(BeEquivalentTo(42)))
				})
			})

			Describe("pea", func() {
				It("creates a containerd container with a running task", func() {
					process, err := container.Run(garden.ProcessSpec{
						ID:    "ctrd-pea-id",
						Image: garden.ImageRef{URI: createPeaRootfsTar()},
						Path:  "/bin/sleep",
						Args:  []string{"10"},
						User:  "alice",
					}, garden.ProcessIO{})
					Expect(err).NotTo(HaveOccurred())

					containers := listContainers("ctr", config.ContainerdSocket)
					Expect(containers).To(ContainSubstring("ctrd-pea-id"))

					processes := listProcesses("ctr", config.ContainerdSocket, "ctrd-pea-id")
					Expect(processes).To(ContainSubstring("ctrd-pea-id"))

					peaProcessPid := pidFromProcessesOutput(processes, "ctrd-pea-id")
					cmdline := readFileString(filepath.Join("/", "proc", peaProcessPid, "cmdline"))
					Expect(cmdline).To(ContainSubstring("/bin/sleep"))

					code, err := process.Wait()
					Expect(err).NotTo(HaveOccurred())
					Expect(code).To(Equal(0))
				})

				It("cleans up pea-debris", func() {
					process, err := container.Run(garden.ProcessSpec{
						ID:    "ctrd-pea-id-2",
						Image: garden.ImageRef{URI: createPeaRootfsTar()},
						Path:  "/bin/echo",
						Args:  []string{"peeeeee"},
						User:  "alice",
					}, garden.ProcessIO{})
					Expect(err).NotTo(HaveOccurred())

					code, err := process.Wait()
					Expect(err).NotTo(HaveOccurred())
					Expect(code).To(Equal(0))

					tasks := listTasks("ctr", config.ContainerdSocket)
					Expect(tasks).NotTo(ContainSubstring("ctrd-pea-id-2"))
					Expect(tasks).To(MatchRegexp(container.Handle() + `\s+\d+\s+RUNNING`))

					containers := listContainers("ctr", config.ContainerdSocket)
					Expect(containers).NotTo(ContainSubstring("ctrd-pea-id-2"))
				})

				It("returns the process exit code", func() {
					process, err := container.Run(garden.ProcessSpec{
						Image: garden.ImageRef{URI: createPeaRootfsTar()},
						Path:  "/bin/sh",
						Args:  []string{"-c", "exit 12"},
					}, garden.ProcessIO{})
					Expect(err).NotTo(HaveOccurred())

					code, err := process.Wait()
					Expect(err).NotTo(HaveOccurred())
					Expect(code).To(Equal(12))
				})

				Describe("Stdio", func() {
					It("connects stdin", func() {
						stdout := gbytes.NewBuffer()
						stdin := bytes.NewBufferString("hello from stdin")
						process, err := container.Run(garden.ProcessSpec{
							Path:  "cat",
							Image: garden.ImageRef{URI: createPeaRootfsTar()},
						}, garden.ProcessIO{
							Stdin:  stdin,
							Stdout: io.MultiWriter(GinkgoWriter, stdout),
						})
						Expect(err).NotTo(HaveOccurred())
						Expect(process.Wait()).To(Equal(0))
						Eventually(stdout).Should(gbytes.Say("hello from stdin"))
					})

					It("connects stdout", func() {
						stdout := new(bytes.Buffer)
						process, err := container.Run(garden.ProcessSpec{
							Path:  "/bin/echo",
							Args:  []string{"-n", "hello world"},
							Image: garden.ImageRef{URI: createPeaRootfsTar()},
						}, garden.ProcessIO{
							Stdout: io.MultiWriter(GinkgoWriter, stdout),
						})
						Expect(err).NotTo(HaveOccurred())
						Expect(process.Wait()).To(Equal(0))

						Expect(stdout.String()).To(Equal("hello world"))
					})

					It("connects stderr", func() {
						stderr := new(bytes.Buffer)
						process, err := container.Run(garden.ProcessSpec{
							Path:  "/bin/sh",
							Args:  []string{"-c", "/bin/echo -n hello error 1>&2"},
							Image: garden.ImageRef{URI: createPeaRootfsTar()},
						}, garden.ProcessIO{
							Stderr: io.MultiWriter(GinkgoWriter, stderr),
						})
						Expect(err).NotTo(HaveOccurred())
						Expect(process.Wait()).To(Equal(0))

						Expect(stderr.String()).To(Equal("hello error"))
					})
				})
			})
		})
	})
})

func listContainers(ctr, socket string) string {
	return runCtr(ctr, socket, []string{"containers", "list"})
}

func listTasks(ctr, socket string) string {
	return runCtr(ctr, socket, []string{"tasks", "list"})
}

func listProcesses(ctr, socket, containerID string) string {
	return runCtr(ctr, socket, []string{"tasks", "ps", containerID})
}

func pidFromProcessesOutput(processesOutput, id string) string {
	// processesOutput expected to be of the form:
	// PID      INFO
	// 23296    -
	// 23437    &ProcessDetails{ExecID:ctrd-pea-id,}

	processesOutputLines := strings.Split(processesOutput, "\n")

	for _, processesOutputLine := range processesOutputLines {
		if strings.Contains(processesOutputLine, id) {
			return strings.Split(processesOutputLine, " ")[0]
		}
	}

	return "0"
}

func runCtr(ctr, socket string, args []string) string {
	defaultArgs := []string{"--address", socket, "--namespace", "garden"}
	cmd := exec.Command(ctr, append(defaultArgs, args...)...)

	session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
	Expect(err).NotTo(HaveOccurred())
	Eventually(session).Should(gexec.Exit(0), string(session.Err.Contents()))

	return string(session.Out.Contents())
}
<commit_msg>Add containerd pea signalling test<commit_after>package gqt_test

import (
	"bytes"
	"io"
	"os/exec"
	"path/filepath"
	"strings"

	"code.cloudfoundry.org/garden"
	"code.cloudfoundry.org/guardian/gqt/runner"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/gexec"
)

var _ = Describe("Containerd", func() {
	var client *runner.RunningGarden

	BeforeEach(func() {
		skipIfNotContainerd()
	})

	JustBeforeEach(func() {
		client = runner.Start(config)
	})

	AfterEach(func() {
		Expect(client.DestroyAndStop()).To(Succeed())
	})

	Describe("creating containers", func() {
		It("creates a containerd container with running init task", func() {
			container, err := client.Create(garden.ContainerSpec{})
			Expect(err).NotTo(HaveOccurred())

			containers := listContainers("ctr", config.ContainerdSocket)
			Expect(containers).To(ContainSubstring(container.Handle()))

			tasks := listTasks("ctr", config.ContainerdSocket)
			Expect(tasks).To(ContainSubstring(container.Handle()))
			Expect(tasks).To(MatchRegexp(container.Handle() + `\s+\d+\s+RUNNING`))
		})
	})

	Describe("destroying a container", func() {
		var container garden.Container

		JustBeforeEach(func() {
			var err error
			container, err = client.Create(garden.ContainerSpec{})
			Expect(err).NotTo(HaveOccurred())
		})

		It("removes the container from ctr lookup", func() {
			err := client.Destroy(container.Handle())
			Expect(err).NotTo(HaveOccurred())

			containers := listContainers("ctr", config.ContainerdSocket)
			Expect(containers).NotTo(ContainSubstring(container.Handle()))
		})
	})

	Describe("running a process in a container", func() {
		var (
			processID string
			container garden.Container
		)

		JustBeforeEach(func() {
			var err error
			container, err = client.Create(garden.ContainerSpec{})
			Expect(err).NotTo(HaveOccurred())
		})

		AfterEach(func() {
			Expect(client.Destroy(container.Handle())).To(Succeed())
		})

		It("succeeds", func() {
			process, err := container.Run(garden.ProcessSpec{
				Path: "/bin/sh",
				Args: []string{"-c", "exit 17"},
			}, garden.ProcessIO{})
			Expect(err).NotTo(HaveOccurred())
			statusCode, err := process.Wait()
			Expect(err).NotTo(HaveOccurred())
			Expect(statusCode).To(Equal(17))
		})

		It("can attach to a process", func() {
			process, err := container.Run(garden.ProcessSpec{
				Path: "/bin/sh",
				Args: []string{"-c", "exit 13"},
			}, garden.ProcessIO{})
			Expect(err).NotTo(HaveOccurred())
			processID = process.ID()

			attachedProcess, err := container.Attach(processID, garden.ProcessIO{})
			Expect(err).NotTo(HaveOccurred())

			exitCode, err := attachedProcess.Wait()
			Expect(err).NotTo(HaveOccurred())
			Expect(exitCode).To(Equal(13))
		})

		Context("when use_containerd_for_processes is enabled", func() {
			BeforeEach(func() {
				config.UseContainerdForProcesses = boolptr(true)
			})

			It("is known about by containerd", func() {
				_, err := container.Run(garden.ProcessSpec{
					ID:   "ctrd-process-id",
					Path: "/bin/sleep",
					Args: []string{"10"},
					Dir:  "/",
				}, garden.ProcessIO{})
				Expect(err).NotTo(HaveOccurred())

				processes := listProcesses("ctr", config.ContainerdSocket, container.Handle())
				Expect(processes).To(ContainSubstring("ctrd-process-id"))
			})

			It("can resolve the user of the process", func() {
				stdout := gbytes.NewBuffer()
				_, err := container.Run(garden.ProcessSpec{
					ID:   "ctrd-process-id",
					Path: "/bin/ps",
					User: "1000",
					Dir:  "/",
				}, garden.ProcessIO{
					Stdout: io.MultiWriter(GinkgoWriter, stdout),
				})
				Expect(err).NotTo(HaveOccurred())
				Eventually(stdout).Should(gbytes.Say("alice"))
			})

			It("can resolve the home directory of the user if none was specified", func() {
				stdout := gbytes.NewBuffer()
				_, err := container.Run(garden.ProcessSpec{
					ID:   "ctrd-process-pwd",
					Path: "/bin/pwd",
					User: "alice",
				}, garden.ProcessIO{
					Stdout: io.MultiWriter(GinkgoWriter, stdout),
				})
				Expect(err).NotTo(HaveOccurred())

				Eventually(stdout).Should(gbytes.Say("/home/alice"))
			})

			It("can run a process without providing an ID", func() {
				stdout := gbytes.NewBuffer()
				_, err := container.Run(garden.ProcessSpec{
					Path: "/bin/echo",
					Args: []string{"hello alice"},
				}, garden.ProcessIO{
					Stdout: io.MultiWriter(GinkgoWriter, stdout),
				})
				Expect(err).NotTo(HaveOccurred())

				Eventually(stdout).Should(gbytes.Say("hello alice"))
			})

			It("can get the exit code of a process", func() {
				process, err := container.Run(garden.ProcessSpec{
					Path: "/bin/sh",
					Args: []string{"-c", "exit 17"},
				}, garden.ProcessIO{})
				Expect(err).NotTo(HaveOccurred())

				exitCode, err := process.Wait()
				Expect(err).NotTo(HaveOccurred())

				Expect(exitCode).To(Equal(17))
			})

			Describe("Stdio", func() {
				It("connects stdin", func() {
					stdout := gbytes.NewBuffer()
					stdin := bytes.NewBufferString("hello from stdin")
					process, err := container.Run(garden.ProcessSpec{
						Path: "cat",
					}, garden.ProcessIO{
						Stdin:  stdin,
						Stdout: io.MultiWriter(GinkgoWriter, stdout),
					})
					Expect(err).NotTo(HaveOccurred())
					Expect(process.Wait()).To(Equal(0))

					Eventually(stdout).Should(gbytes.Say("hello from stdin"))
				})

				It("connects stdout", func() {
					stdout := new(bytes.Buffer)
					process, err := container.Run(garden.ProcessSpec{
						Path: "/bin/echo",
						Args: []string{"-n", "hello world"},
					}, garden.ProcessIO{
						Stdout: io.MultiWriter(GinkgoWriter, stdout),
					})
					Expect(err).NotTo(HaveOccurred())
					Expect(process.Wait()).To(Equal(0))

					Expect(stdout.String()).To(Equal("hello world"))
				})

				It("connects stderr", func() {
					stderr := new(bytes.Buffer)
					process, err := container.Run(garden.ProcessSpec{
						Path: "/bin/sh",
						Args: []string{"-c", "/bin/echo -n hello error 1>&2"},
					}, garden.ProcessIO{
						Stderr: io.MultiWriter(GinkgoWriter, stderr),
					})
					Expect(err).NotTo(HaveOccurred())
					Expect(process.Wait()).To(Equal(0))

					Expect(stderr.String()).To(Equal("hello error"))
				})
			})

			Describe("Signalling", func() {
				It("forwards signals to the process", func() {
					buffer := gbytes.NewBuffer()
					proc, err := container.Run(garden.ProcessSpec{
						Path: "sh",
						Args: []string{"-c", `
					trap 'exit 42' TERM

					while true; do
					  echo 'sleeping'
					  sleep 1
					done
				`},
					}, garden.ProcessIO{
						Stdout: buffer,
					})
					Expect(err).NotTo(HaveOccurred())

					Eventually(buffer).Should(gbytes.Say("sleeping"))

					err = proc.Signal(garden.SignalTerminate)
					Expect(err).NotTo(HaveOccurred())

					status := make(chan int)
					go func() {
						exit, err := proc.Wait()
						Expect(err).NotTo(HaveOccurred())
						status <- exit
					}()

					Eventually(status).Should(Receive(BeEquivalentTo(42)))
				})
			})

			Describe("pea", func() {
				It("creates a containerd container with a running task", func() {
					process, err := container.Run(garden.ProcessSpec{
						ID:    "ctrd-pea-id",
						Image: garden.ImageRef{URI: createPeaRootfsTar()},
						Path:  "/bin/sleep",
						Args:  []string{"10"},
						User:  "alice",
					}, garden.ProcessIO{})
					Expect(err).NotTo(HaveOccurred())

					containers := listContainers("ctr", config.ContainerdSocket)
					Expect(containers).To(ContainSubstring("ctrd-pea-id"))

					processes := listProcesses("ctr", config.ContainerdSocket, "ctrd-pea-id")
					Expect(processes).To(ContainSubstring("ctrd-pea-id"))

					peaProcessPid := pidFromProcessesOutput(processes, "ctrd-pea-id")
					cmdline := readFileString(filepath.Join("/", "proc", peaProcessPid, "cmdline"))
					Expect(cmdline).To(ContainSubstring("/bin/sleep"))

					code, err := process.Wait()
					Expect(err).NotTo(HaveOccurred())
					Expect(code).To(Equal(0))
				})

				It("cleans up pea-debris", func() {
					process, err := container.Run(garden.ProcessSpec{
						ID:    "ctrd-pea-id-2",
						Image: garden.ImageRef{URI: createPeaRootfsTar()},
						Path:  "/bin/echo",
						Args:  []string{"peeeeee"},
						User:  "alice",
					}, garden.ProcessIO{})
					Expect(err).NotTo(HaveOccurred())

					code, err := process.Wait()
					Expect(err).NotTo(HaveOccurred())
					Expect(code).To(Equal(0))

					tasks := listTasks("ctr", config.ContainerdSocket)
					Expect(tasks).NotTo(ContainSubstring("ctrd-pea-id-2"))
					Expect(tasks).To(MatchRegexp(container.Handle() + `\s+\d+\s+RUNNING`))

					containers := listContainers("ctr", config.ContainerdSocket)
					Expect(containers).NotTo(ContainSubstring("ctrd-pea-id-2"))
				})

				It("returns the process exit code", func() {
					process, err := container.Run(garden.ProcessSpec{
						Image: garden.ImageRef{URI: createPeaRootfsTar()},
						Path:  "/bin/sh",
						Args:  []string{"-c", "exit 12"},
					}, garden.ProcessIO{})
					Expect(err).NotTo(HaveOccurred())

					code, err := process.Wait()
					Expect(err).NotTo(HaveOccurred())
					Expect(code).To(Equal(12))
				})

				Describe("Stdio", func() {
					It("connects stdin", func() {
						stdout := gbytes.NewBuffer()
						stdin := bytes.NewBufferString("hello from stdin")
						process, err := container.Run(garden.ProcessSpec{
							Path:  "cat",
							Image: garden.ImageRef{URI: createPeaRootfsTar()},
						}, garden.ProcessIO{
							Stdin:  stdin,
							Stdout: io.MultiWriter(GinkgoWriter, stdout),
						})
						Expect(err).NotTo(HaveOccurred())
						Expect(process.Wait()).To(Equal(0))
						Eventually(stdout).Should(gbytes.Say("hello from stdin"))
					})

					It("connects stdout", func() {
						stdout := new(bytes.Buffer)
						process, err := container.Run(garden.ProcessSpec{
							Path:  "/bin/echo",
							Args:  []string{"-n", "hello world"},
							Image: garden.ImageRef{URI: createPeaRootfsTar()},
						}, garden.ProcessIO{
							Stdout: io.MultiWriter(GinkgoWriter, stdout),
						})
						Expect(err).NotTo(HaveOccurred())
						Expect(process.Wait()).To(Equal(0))

						Expect(stdout.String()).To(Equal("hello world"))
					})

					It("connects stderr", func() {
						stderr := new(bytes.Buffer)
						process, err := container.Run(garden.ProcessSpec{
							Path:  "/bin/sh",
							Args:  []string{"-c", "/bin/echo -n hello error 1>&2"},
							Image: garden.ImageRef{URI: createPeaRootfsTar()},
						}, garden.ProcessIO{
							Stderr: io.MultiWriter(GinkgoWriter, stderr),
						})
						Expect(err).NotTo(HaveOccurred())
						Expect(process.Wait()).To(Equal(0))

						Expect(stderr.String()).To(Equal("hello error"))
					})
				})

				Describe("Signalling", func() {
					It("forwards signals to the pea", func() {
						buffer := gbytes.NewBuffer()
						proc, err := container.Run(garden.ProcessSpec{
							Path:  "sh",
							Image: garden.ImageRef{URI: createPeaRootfsTar()},
							Args: []string{"-c", `
					trap 'exit 42' TERM

					while true; do
					  echo 'sleeping'
					  sleep 1
					done
				`},
						}, garden.ProcessIO{
							Stdout: buffer,
						})
						Expect(err).NotTo(HaveOccurred())

						Eventually(buffer).Should(gbytes.Say("sleeping"))

						err = proc.Signal(garden.SignalTerminate)
						Expect(err).NotTo(HaveOccurred())

						status := make(chan int)
						go func() {
							exit, err := proc.Wait()
							Expect(err).NotTo(HaveOccurred())
							status <- exit
						}()

						Eventually(status).Should(Receive(BeEquivalentTo(42)))
					})
				})
			})
		})
	})
})

func listContainers(ctr, socket string) string {
	return runCtr(ctr, socket, []string{"containers", "list"})
}

func listTasks(ctr, socket string) string {
	return runCtr(ctr, socket, []string{"tasks", "list"})
}

func listProcesses(ctr, socket, containerID string) string {
	return runCtr(ctr, socket, []string{"tasks", "ps", containerID})
}

func pidFromProcessesOutput(processesOutput, id string) string {
	// processesOutput expected to be of the form:
	// PID      INFO
	// 23296    -
	// 23437    &ProcessDetails{ExecID:ctrd-pea-id,}

	processesOutputLines := strings.Split(processesOutput, "\n")

	for _, processesOutputLine := range processesOutputLines {
		if strings.Contains(processesOutputLine, id) {
			return strings.Split(processesOutputLine, " ")[0]
		}
	}

	return "0"
}

func runCtr(ctr, socket string, args []string) string {
	defaultArgs := []string{"--address", socket, "--namespace", "garden"}
	cmd := exec.Command(ctr, append(defaultArgs, args...)...)

	session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
	Expect(err).NotTo(HaveOccurred())
	Eventually(session).Should(gexec.Exit(0), string(session.Err.Contents()))

	return string(session.Out.Contents())
}
<|endoftext|>


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

package buildkite

import (
	"fmt"
	"time"
)

// BuildsService handles communication with the build related
// methods of the buildkite API.
//
// buildkite API docs: https://buildkite.com/docs/api/builds
type BuildsService struct {
	client *Client
}

// Author of a commit (used in CreateBuild)
type Author struct {
	Name  string `json:"name,omitempty"`
	Email string `json:"email,omitempty"`
}

// Create a build.
type CreateBuild struct {
	Commit  string `json:"commit"`
	Branch  string `json:"branch"`
	Message string `json:"message"`

	// Optional fields
	Author                      Author            `json:"author,omitempty"`
	Env                         map[string]string `json:"env,omitempty"`
	MetaData                    map[string]string `json:"meta_data,omitempty"`
	IgnorePipelineBranchFilters bool              `json:"ignore_pipeline_branch_filters,omitempty"`
}

// Creator represents who created a build
type Creator struct {
	AvatarURL string     `json:"avatar_url"`
	CreatedAt *Timestamp `json:"created_at"`
	Email     string     `json:"email"`
	ID        string     `json:"id"`
	Name      string     `json:"name"`
}

// Build represents a build which has run in buildkite
type Build struct {
	ID          *string                `json:"id,omitempty"`
	URL         *string                `json:"url,omitempty"`
	WebURL      *string                `json:"web_url,omitempty"`
	Number      *int                   `json:"number,omitempty"`
	State       *string                `json:"state,omitempty"`
	Message     *string                `json:"message,omitempty"`
	Commit      *string                `json:"commit,omitempty"`
	Branch      *string                `json:"branch,omitempty"`
	Env         map[string]interface{} `json:"env,omitempty"`
	CreatedAt   *Timestamp             `json:"created_at,omitempty"`
	ScheduledAt *Timestamp             `json:"scheduled_at,omitempty"`
	StartedAt   *Timestamp             `json:"started_at,omitempty"`
	FinishedAt  *Timestamp             `json:"finished_at,omitempty"`
	MetaData    interface{}            `json:"meta_data,omitempty"`
	Creator     *Creator               `json:"creator,omitempty"`

	// jobs run during the build
	Jobs []*Job `json:"jobs,omitempty"`

	// the pipeline this build is associated with
	Pipeline *Pipeline `json:"pipeline,omitempty"`
}

// Job represents a job run during a build in buildkite
type Job struct {
	ID              *string    `json:"id,omitempty"`
	Type            *string    `json:"type,omitempty"`
	Name            *string    `json:"name,omitempty"`
	State           *string    `json:"state,omitempty"`
	LogsURL         *string    `json:"logs_url,omitempty"`
	RawLogsURL      *string    `json:"raw_log_url,omitempty"`
	Command         *string    `json:"command,omitempty"`
	ExitStatus      *int       `json:"exit_status,omitempty"`
	ArtifactPaths   *string    `json:"artifact_paths,omitempty"`
	CreatedAt       *Timestamp `json:"created_at,omitempty"`
	ScheduledAt     *Timestamp `json:"scheduled_at,omitempty"`
	StartedAt       *Timestamp `json:"started_at,omitempty"`
	FinishedAt      *Timestamp `json:"finished_at,omitempty"`
	Agent           Agent      `json:"agent,omitempty"`
	AgentQueryRules []string   `json:"agent_query_rules,omitempty"`
	WebURL          string     `json:"web_url"`
}

// BuildsListOptions specifies the optional parameters to the
// BuildsService.List method.
type BuildsListOptions struct {

	// Filters the results by the user who created the build
	Creator string `url:"creator,omitempty"`

	// Filters the results by builds created on or after the given time
	CreatedFrom time.Time `url:"created_from,omitempty"`

	// Filters the results by builds created before the given time
	CreatedTo time.Time `url:"created_to,omitempty"`

	// Filters the results by builds finished on or after the given time
	FinishedFrom time.Time `url:"finished_from,omitempty"`

	// State of builds to list.  Possible values are: running, scheduled, passed,
	// failed, canceled, skipped and not_run. Default is "".
	State []string `url:"state,brackets,omitempty"`

	// Branch filter by the name of the branch. Default is "".
	Branch string `url:"branch,omitempty"`

	ListOptions
}

func (as *BuildsService) Create(org string, pipeline string, b *CreateBuild) (*Build, *Response, error) {
	u := fmt.Sprintf("v2/organizations/%s/pipelines/%s/builds", org, pipeline)

	req, err := as.client.NewRequest("POST", u, b)
	if err != nil {
		return nil, nil, err
	}

	build := new(Build)
	resp, err := as.client.Do(req, build)
	if err != nil {
		return nil, resp, err
	}

	return build, resp, err
}

// Get fetches a build.
//
// buildkite API docs: https://buildkite.com/docs/api/builds#get-a-build
func (as *BuildsService) Get(org string, pipeline string, id string) (*Build, *Response, error) {
	u := fmt.Sprintf("v2/organizations/%s/pipelines/%s/builds/%s", org, pipeline, id)

	req, err := as.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	build := new(Build)
	resp, err := as.client.Do(req, build)
	if err != nil {
		return nil, resp, err
	}

	return build, resp, err
}

// List the builds for the current user.
//
// buildkite API docs: https://buildkite.com/docs/api/builds#list-all-builds
func (bs *BuildsService) List(opt *BuildsListOptions) ([]Build, *Response, error) {
	var u string

	u = fmt.Sprintf("v2/builds")

	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := bs.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	orgs := new([]Build)
	resp, err := bs.client.Do(req, orgs)
	if err != nil {
		return nil, resp, err
	}

	return *orgs, resp, err
}

// ListByOrg lists the builds within the specified orginisation.
//
// buildkite API docs: https://buildkite.com/docs/api/builds#list-builds-for-an-organization
func (bs *BuildsService) ListByOrg(org string, opt *BuildsListOptions) ([]Build, *Response, error) {
	var u string

	u = fmt.Sprintf("v2/organizations/%s/builds", org)

	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := bs.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	orgs := new([]Build)
	resp, err := bs.client.Do(req, orgs)
	if err != nil {
		return nil, resp, err
	}

	return *orgs, resp, err
}

// ListByPipeline lists the builds for a pipeline within the specified originisation.
//
// buildkite API docs: https://buildkite.com/docs/api/builds#list-builds-for-a-pipeline
func (bs *BuildsService) ListByPipeline(org string, pipeline string, opt *BuildsListOptions) ([]Build, *Response, error) {
	var u string

	u = fmt.Sprintf("v2/organizations/%s/pipelines/%s/builds", org, pipeline)

	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := bs.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	orgs := new([]Build)
	resp, err := bs.client.Do(req, orgs)
	if err != nil {
		return nil, resp, err
	}

	return *orgs, resp, err
}
<commit_msg>Support Job.runnable_at<commit_after>// Copyright 2014 Mark Wolfe. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package buildkite

import (
	"fmt"
	"time"
)

// BuildsService handles communication with the build related
// methods of the buildkite API.
//
// buildkite API docs: https://buildkite.com/docs/api/builds
type BuildsService struct {
	client *Client
}

// Author of a commit (used in CreateBuild)
type Author struct {
	Name  string `json:"name,omitempty"`
	Email string `json:"email,omitempty"`
}

// Create a build.
type CreateBuild struct {
	Commit  string `json:"commit"`
	Branch  string `json:"branch"`
	Message string `json:"message"`

	// Optional fields
	Author                      Author            `json:"author,omitempty"`
	Env                         map[string]string `json:"env,omitempty"`
	MetaData                    map[string]string `json:"meta_data,omitempty"`
	IgnorePipelineBranchFilters bool              `json:"ignore_pipeline_branch_filters,omitempty"`
}

// Creator represents who created a build
type Creator struct {
	AvatarURL string     `json:"avatar_url"`
	CreatedAt *Timestamp `json:"created_at"`
	Email     string     `json:"email"`
	ID        string     `json:"id"`
	Name      string     `json:"name"`
}

// Build represents a build which has run in buildkite
type Build struct {
	ID          *string                `json:"id,omitempty"`
	URL         *string                `json:"url,omitempty"`
	WebURL      *string                `json:"web_url,omitempty"`
	Number      *int                   `json:"number,omitempty"`
	State       *string                `json:"state,omitempty"`
	Message     *string                `json:"message,omitempty"`
	Commit      *string                `json:"commit,omitempty"`
	Branch      *string                `json:"branch,omitempty"`
	Env         map[string]interface{} `json:"env,omitempty"`
	CreatedAt   *Timestamp             `json:"created_at,omitempty"`
	ScheduledAt *Timestamp             `json:"scheduled_at,omitempty"`
	StartedAt   *Timestamp             `json:"started_at,omitempty"`
	FinishedAt  *Timestamp             `json:"finished_at,omitempty"`
	MetaData    interface{}            `json:"meta_data,omitempty"`
	Creator     *Creator               `json:"creator,omitempty"`

	// jobs run during the build
	Jobs []*Job `json:"jobs,omitempty"`

	// the pipeline this build is associated with
	Pipeline *Pipeline `json:"pipeline,omitempty"`
}

// Job represents a job run during a build in buildkite
type Job struct {
	ID              *string    `json:"id,omitempty"`
	Type            *string    `json:"type,omitempty"`
	Name            *string    `json:"name,omitempty"`
	State           *string    `json:"state,omitempty"`
	LogsURL         *string    `json:"logs_url,omitempty"`
	RawLogsURL      *string    `json:"raw_log_url,omitempty"`
	Command         *string    `json:"command,omitempty"`
	ExitStatus      *int       `json:"exit_status,omitempty"`
	ArtifactPaths   *string    `json:"artifact_paths,omitempty"`
	CreatedAt       *Timestamp `json:"created_at,omitempty"`
	ScheduledAt     *Timestamp `json:"scheduled_at,omitempty"`
	RunnableAt      *Timestamp `json:"runnable_at,omitempty"`
	StartedAt       *Timestamp `json:"started_at,omitempty"`
	FinishedAt      *Timestamp `json:"finished_at,omitempty"`
	Agent           Agent      `json:"agent,omitempty"`
	AgentQueryRules []string   `json:"agent_query_rules,omitempty"`
	WebURL          string     `json:"web_url"`
}

// BuildsListOptions specifies the optional parameters to the
// BuildsService.List method.
type BuildsListOptions struct {

	// Filters the results by the user who created the build
	Creator string `url:"creator,omitempty"`

	// Filters the results by builds created on or after the given time
	CreatedFrom time.Time `url:"created_from,omitempty"`

	// Filters the results by builds created before the given time
	CreatedTo time.Time `url:"created_to,omitempty"`

	// Filters the results by builds finished on or after the given time
	FinishedFrom time.Time `url:"finished_from,omitempty"`

	// State of builds to list.  Possible values are: running, scheduled, passed,
	// failed, canceled, skipped and not_run. Default is "".
	State []string `url:"state,brackets,omitempty"`

	// Branch filter by the name of the branch. Default is "".
	Branch string `url:"branch,omitempty"`

	ListOptions
}

func (as *BuildsService) Create(org string, pipeline string, b *CreateBuild) (*Build, *Response, error) {
	u := fmt.Sprintf("v2/organizations/%s/pipelines/%s/builds", org, pipeline)

	req, err := as.client.NewRequest("POST", u, b)
	if err != nil {
		return nil, nil, err
	}

	build := new(Build)
	resp, err := as.client.Do(req, build)
	if err != nil {
		return nil, resp, err
	}

	return build, resp, err
}

// Get fetches a build.
//
// buildkite API docs: https://buildkite.com/docs/api/builds#get-a-build
func (as *BuildsService) Get(org string, pipeline string, id string) (*Build, *Response, error) {
	u := fmt.Sprintf("v2/organizations/%s/pipelines/%s/builds/%s", org, pipeline, id)

	req, err := as.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	build := new(Build)
	resp, err := as.client.Do(req, build)
	if err != nil {
		return nil, resp, err
	}

	return build, resp, err
}

// List the builds for the current user.
//
// buildkite API docs: https://buildkite.com/docs/api/builds#list-all-builds
func (bs *BuildsService) List(opt *BuildsListOptions) ([]Build, *Response, error) {
	var u string

	u = fmt.Sprintf("v2/builds")

	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := bs.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	orgs := new([]Build)
	resp, err := bs.client.Do(req, orgs)
	if err != nil {
		return nil, resp, err
	}

	return *orgs, resp, err
}

// ListByOrg lists the builds within the specified orginisation.
//
// buildkite API docs: https://buildkite.com/docs/api/builds#list-builds-for-an-organization
func (bs *BuildsService) ListByOrg(org string, opt *BuildsListOptions) ([]Build, *Response, error) {
	var u string

	u = fmt.Sprintf("v2/organizations/%s/builds", org)

	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := bs.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	orgs := new([]Build)
	resp, err := bs.client.Do(req, orgs)
	if err != nil {
		return nil, resp, err
	}

	return *orgs, resp, err
}

// ListByPipeline lists the builds for a pipeline within the specified originisation.
//
// buildkite API docs: https://buildkite.com/docs/api/builds#list-builds-for-a-pipeline
func (bs *BuildsService) ListByPipeline(org string, pipeline string, opt *BuildsListOptions) ([]Build, *Response, error) {
	var u string

	u = fmt.Sprintf("v2/organizations/%s/pipelines/%s/builds", org, pipeline)

	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}

	req, err := bs.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	orgs := new([]Build)
	resp, err := bs.client.Do(req, orgs)
	if err != nil {
		return nil, resp, err
	}

	return *orgs, resp, err
}
<|endoftext|>


<commit_before>package golisp

import (
    "bufio"
    "fmt"
    "os"
)

func ReadLine(prompt *string) *string {
    if prompt != nil {
        fmt.Printf("%s", *prompt)
    }

    s := bufio.NewScanner(os.Stdin)
    s.Scan()

    result := s.Text()
    return &result
}

func AddHistory(line string) {
    // TODO
}

func ClearHistory() {
    // TODO
}

func WriteHistoryToFile(fileName string) {
    // TODO
}

func LoadHistoryFromFile(fileName string) {
    // TODO
}

func TruncateHistoryFile(fileName string, left int) {
    // TODO
}
<commit_msg>Added copyright noticce.<commit_after>// Copyright 2014 SteelSeries ApS.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This package implements a basic LISP interpretor for embedding in a go program for scripting.
// This file dummies out the readline library as it is not supported
// or required on Windows.

package golisp

import (
	"bufio"
	"fmt"
	"os"
)

func ReadLine(prompt *string) *string {
	if prompt != nil {
		fmt.Printf("%s", *prompt)
	}

	s := bufio.NewScanner(os.Stdin)
	s.Scan()

	result := s.Text()
	return &result
}

func AddHistory(line string) {
	// TODO
}

func ClearHistory() {
	// TODO
}

func WriteHistoryToFile(fileName string) {
	// TODO
}

func LoadHistoryFromFile(fileName string) {
	// TODO
}

func TruncateHistoryFile(fileName string, left int) {
	// TODO
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"strings"

	"github.com/mackerelio/checkers"
	"github.com/ziutek/mymysql/mysql"
	_ "github.com/ziutek/mymysql/native"
)

type mysqlSetting struct {
	Host string `short:"h" long:"host" default:"localhost" description:"Hostname"`
	Port string `short:"p" long:"port" default:"3306" description:"Port"`
	User string `short:"u" long:"user" default:"root" description:"Username"`
	Pass string `short:"P" long:"password" default:"" description:"Password"`
}

var commands = map[string](func([]string) *checkers.Checker){
	"replication": checkReplication,
	"connection":  checkConnection,
}

func separateSub(argv []string) (string, []string) {
	if len(argv) == 0 || strings.HasPrefix(argv[0], "-") {
		return "", argv
	}
	return argv[0], argv[1:]
}

func main() {
	subCmd, argv := separateSub(os.Args[1:])
	fn, ok := commands[subCmd]
	if !ok {
		fmt.Println(`Usage:
  check-mysql [subcommand] [OPTIONS]

Subcommand:
  connection
  replication`)
		os.Exit(1)
	}
	ckr := fn(argv)
	ckr.Name = fmt.Sprintf("MySQL %s", strings.ToUpper(string(subCmd[0]))+subCmd[1:])
	ckr.Exit()
}

func newMySQL(m mysqlSetting) mysql.Conn {
	target := fmt.Sprintf("%s:%s", m.Host, m.Port)
	return mysql.New("tcp", "", target, m.User, m.Pass, "")
}
<commit_msg>update help message<commit_after>package main

import (
	"fmt"
	"os"
	"strings"

	"github.com/mackerelio/checkers"
	"github.com/ziutek/mymysql/mysql"
	_ "github.com/ziutek/mymysql/native"
)

type mysqlSetting struct {
	Host string `short:"h" long:"host" default:"localhost" description:"Hostname"`
	Port string `short:"p" long:"port" default:"3306" description:"Port"`
	User string `short:"u" long:"user" default:"root" description:"Username"`
	Pass string `short:"P" long:"password" default:"" description:"Password"`
}

var commands = map[string](func([]string) *checkers.Checker){
	"replication": checkReplication,
	"connection":  checkConnection,
}

func separateSub(argv []string) (string, []string) {
	if len(argv) == 0 || strings.HasPrefix(argv[0], "-") {
		return "", argv
	}
	return argv[0], argv[1:]
}

func main() {
	subCmd, argv := separateSub(os.Args[1:])
	fn, ok := commands[subCmd]
	if !ok {
		fmt.Println(`Usage:
  check-mysql [subcommand] [OPTIONS]

SubCommands:`)
		for k := range commands {
			fmt.Printf("  %s\n", k)
		}
		os.Exit(1)
	}
	ckr := fn(argv)
	ckr.Name = fmt.Sprintf("MySQL %s", strings.ToUpper(string(subCmd[0]))+subCmd[1:])
	ckr.Exit()
}

func newMySQL(m mysqlSetting) mysql.Conn {
	target := fmt.Sprintf("%s:%s", m.Host, m.Port)
	return mysql.New("tcp", "", target, m.User, m.Pass, "")
}
<|endoftext|>


<commit_before>// Copyright 2015, Joe Tsai. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.

package bzip2

import (
	"bytes"
	"io"
	"testing"

	"github.com/dsnet/compress/internal/testutil"
)

var testdata = []struct {
	name  string
	data  []byte
	ratio float64 // The minimum expected ratio (uncompressed / compressed)
}{
	{"Nil", nil, 0},
	{"Binary", testutil.MustLoadFile("../testdata/binary.bin"), 5.68},
	{"Digits", testutil.MustLoadFile("../testdata/digits.txt"), 2.22},
	{"Huffman", testutil.MustLoadFile("../testdata/huffman.txt"), 1.24},
	{"Random", testutil.MustLoadFile("../testdata/random.bin"), 0.98},
	{"Repeats", testutil.MustLoadFile("../testdata/repeats.bin"), 3.93},
	{"Twain", testutil.MustLoadFile("../testdata/twain.txt"), 2.99},
	{"Zeros", testutil.MustLoadFile("../testdata/zeros.bin"), 5825.0},
}

var levels = []struct {
	name  string
	level int
}{
	{"Speed", BestSpeed},
	{"Default", DefaultCompression},
	{"Compression", BestCompression},
}

var sizes = []struct {
	name string
	size int
}{
	{"1e4", 1e4},
	{"1e5", 1e5},
	{"1e6", 1e6},
}

func TestRoundTrip(t *testing.T) {
	for i, v := range testdata {
		var buf1, buf2 bytes.Buffer

		// Compress the input.
		wr, err := NewWriter(&buf1, nil)
		if err != nil {
			t.Errorf("test %d, NewWriter() = (_, %v), want (_, nil)", i, err)
		}
		n, err := io.Copy(wr, bytes.NewReader(v.data))
		if n != int64(len(v.data)) || err != nil {
			t.Errorf("test %d, Copy() = (%d, %v), want (%d, nil)", i, n, err, len(v.data))
		}
		if err := wr.Close(); err != nil {
			t.Errorf("test %d, Close() = %v, want nil", i, err)
		}

		ratio := float64(len(v.data)) / float64(buf1.Len())
		if ratio < v.ratio {
			t.Errorf("test %d, poor compression ratio: %0.2f < %0.2f", i, ratio, v.ratio)
		}

		// Write a canary byte to ensure this does not get read.
		buf1.WriteByte(0x7a)

		// Decompress the output.
		rd, err := NewReader(&buf1, nil)
		if err != nil {
			t.Errorf("test %d, NewReader() = (_, %v), want (_, nil)", i, err)
		}
		n, err = io.Copy(&buf2, rd)
		if n != int64(len(v.data)) || err != nil {
			t.Errorf("test %d, Copy() = (%d, %v), want (%d, nil)", i, n, err, len(v.data))
		}
		if err := rd.Close(); err != nil {
			t.Errorf("test %d, Close() = %v, want nil", i, err)
		}
		if !bytes.Equal(buf2.Bytes(), v.data) {
			t.Errorf("test %d, output data mismatch", i)
		}

		// Read back the canary byte.
		if v, _ := buf1.ReadByte(); v != 0x7a {
			t.Errorf("Read consumed more data than necessary")
		}
	}
}

func runBenchmarks(b *testing.B, f func(b *testing.B, buf []byte, lvl int)) {
	for _, td := range testdata {
		if len(td.data) == 0 {
			continue
		}
		if testing.Short() && !(td.name == "Twain" || td.name == "Digits") {
			continue
		}
		for _, tl := range levels {
			for _, ts := range sizes {
				buf := testutil.ResizeData(td.data, ts.size)
				b.Run(td.name+"/"+tl.name+"/"+ts.name, func(b *testing.B) {
					f(b, buf, tl.level)
				})
			}
		}
	}
}
<commit_msg>bzip2: use C library to validate tests<commit_after>// Copyright 2015, Joe Tsai. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.

package bzip2

import (
	"bytes"
	"flag"
	"io"
	"os/exec"
	"testing"

	"github.com/dsnet/compress/internal/testutil"
)

var zcheck = flag.Bool("zcheck", false, "verify test vectors with C bzip2 library")

func pyCompress(input []byte) ([]byte, error) {
	var buf bytes.Buffer
	cmd := exec.Command("python", "-c", "import sys, bz2; sys.stdout.write(bz2.compress(sys.stdin.read()))")
	cmd.Stdin = bytes.NewReader(input)
	cmd.Stdout = &buf
	err := cmd.Run()
	return buf.Bytes(), err
}

func pyDecompress(input []byte) ([]byte, error) {
	var buf bytes.Buffer
	cmd := exec.Command("python", "-c", "import sys, bz2; sys.stdout.write(bz2.decompress(sys.stdin.read()))")
	cmd.Stdin = bytes.NewReader(input)
	cmd.Stdout = &buf
	err := cmd.Run()
	return buf.Bytes(), err
}

var testdata = []struct {
	name  string
	data  []byte
	ratio float64 // The minimum expected ratio (uncompressed / compressed)
}{
	{"Nil", nil, 0},
	{"Binary", testutil.MustLoadFile("../testdata/binary.bin"), 5.68},
	{"Digits", testutil.MustLoadFile("../testdata/digits.txt"), 2.22},
	{"Huffman", testutil.MustLoadFile("../testdata/huffman.txt"), 1.24},
	{"Random", testutil.MustLoadFile("../testdata/random.bin"), 0.98},
	{"Repeats", testutil.MustLoadFile("../testdata/repeats.bin"), 3.93},
	{"Twain", testutil.MustLoadFile("../testdata/twain.txt"), 2.99},
	{"Zeros", testutil.MustLoadFile("../testdata/zeros.bin"), 5825.0},
}

var levels = []struct {
	name  string
	level int
}{
	{"Speed", BestSpeed},
	{"Default", DefaultCompression},
	{"Compression", BestCompression},
}

var sizes = []struct {
	name string
	size int
}{
	{"1e4", 1e4},
	{"1e5", 1e5},
	{"1e6", 1e6},
}

func TestRoundTrip(t *testing.T) {
	for _, v := range testdata {
		t.Run(v.name, func(t *testing.T) {
			var buf1, buf2 bytes.Buffer

			// Compress the input.
			wr, err := NewWriter(&buf1, nil)
			if err != nil {
				t.Errorf("NewWriter() = (_, %v), want (_, nil)", err)
			}
			n, err := io.Copy(wr, bytes.NewReader(v.data))
			if n != int64(len(v.data)) || err != nil {
				t.Errorf("Copy() = (%d, %v), want (%d, nil)", n, err, len(v.data))
			}
			if err := wr.Close(); err != nil {
				t.Errorf("Close() = %v, want nil", err)
			}

			// Verify that the compression ratio is within expected bounds.
			ratio := float64(len(v.data)) / float64(buf1.Len())
			if ratio < v.ratio {
				t.Errorf("poor compression ratio: %0.2f < %0.2f", ratio, v.ratio)
			}

			// Verify that the C library can decompress the output of Writer and
			// that the Reader can decompress the output of the C library.
			if *zcheck {
				zd, err := pyDecompress(buf1.Bytes())
				if err != nil {
					t.Errorf("unexpected pyDecompress error: %v", err)
				}
				if !bytes.Equal(zd, v.data) {
					t.Errorf("output data mismatch")
				}
				zc, err := pyCompress(v.data)
				if err != nil {
					t.Errorf("unexpected pyCompress error: %v", err)
				}
				buf1.Reset()
				buf1.Write(zc) // Use output of C library for Reader test
			}

			// Write a canary byte to ensure this does not get read.
			buf1.WriteByte(0x7a)

			// Decompress the output.
			rd, err := NewReader(&buf1, nil)
			if err != nil {
				t.Errorf("NewReader() = (_, %v), want (_, nil)", err)
			}
			n, err = io.Copy(&buf2, rd)
			if n != int64(len(v.data)) || err != nil {
				t.Errorf("Copy() = (%d, %v), want (%d, nil)", n, err, len(v.data))
			}
			if err := rd.Close(); err != nil {
				t.Errorf("Close() = %v, want nil", err)
			}
			if !bytes.Equal(buf2.Bytes(), v.data) {
				t.Errorf("output data mismatch")
			}

			// Read back the canary byte.
			if v, _ := buf1.ReadByte(); v != 0x7a {
				t.Errorf("Read consumed more data than necessary")
			}
		})
	}
}

func runBenchmarks(b *testing.B, f func(b *testing.B, buf []byte, lvl int)) {
	for _, td := range testdata {
		if len(td.data) == 0 {
			continue
		}
		if testing.Short() && !(td.name == "Twain" || td.name == "Digits") {
			continue
		}
		for _, tl := range levels {
			for _, ts := range sizes {
				buf := testutil.ResizeData(td.data, ts.size)
				b.Run(td.name+"/"+tl.name+"/"+ts.name, func(b *testing.B) {
					f(b, buf, tl.level)
				})
			}
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2015, Joe Tsai. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.

package bzip2

import (
	"bytes"
	"errors"
	"flag"
	"io"
	"os/exec"
	"strings"
	"testing"

	"github.com/dsnet/compress/internal/testutil"
)

var zcheck = flag.Bool("zcheck", false, "verify test vectors with C bzip2 library")

func cmdCompress(input []byte) ([]byte, error)   { return cmdExec("-z", input) }
func cmdDecompress(input []byte) ([]byte, error) { return cmdExec("-d", input) }

// cmdExec executes the bzip2 tool, passing the input in as stdin.
// It returns the stdout and an error.
func cmdExec(pyc string, input []byte) ([]byte, error) {
	var bo, be bytes.Buffer
	cmd := exec.Command("bzip2", pyc)
	cmd.Stdin = bytes.NewReader(input)
	cmd.Stdout = &bo
	cmd.Stderr = &be
	err := cmd.Run()
	ss := strings.Split(strings.TrimSpace(be.String()), "\n")
	if len(ss) > 0 && ss[len(ss)-1] != "" {
		// Assume any stderr indicates an error and last line is the message.
		return nil, errors.New(ss[len(ss)-1])
	}
	return bo.Bytes(), err
}

var testdata = []struct {
	name  string
	data  []byte
	ratio float64 // The minimum expected ratio (uncompressed / compressed)
}{
	{"Nil", nil, 0},
	{"Binary", testutil.MustLoadFile("../testdata/binary.bin"), 5.68},
	{"Digits", testutil.MustLoadFile("../testdata/digits.txt"), 2.22},
	{"Huffman", testutil.MustLoadFile("../testdata/huffman.txt"), 1.24},
	{"Random", testutil.MustLoadFile("../testdata/random.bin"), 0.98},
	{"Repeats", testutil.MustLoadFile("../testdata/repeats.bin"), 3.93},
	{"Twain", testutil.MustLoadFile("../testdata/twain.txt"), 2.99},
	{"Zeros", testutil.MustLoadFile("../testdata/zeros.bin"), 5825.0},
}

var levels = []struct {
	name  string
	level int
}{
	{"Speed", BestSpeed},
	{"Default", DefaultCompression},
	{"Compression", BestCompression},
}

var sizes = []struct {
	name string
	size int
}{
	{"1e4", 1e4},
	{"1e5", 1e5},
	{"1e6", 1e6},
}

func TestRoundTrip(t *testing.T) {
	for _, v := range testdata {
		v := v
		t.Run(v.name, func(t *testing.T) {
			t.Parallel()

			var buf1, buf2 bytes.Buffer

			// Compress the input.
			wr, err := NewWriter(&buf1, nil)
			if err != nil {
				t.Errorf("NewWriter() = (_, %v), want (_, nil)", err)
			}
			n, err := io.Copy(wr, bytes.NewReader(v.data))
			if n != int64(len(v.data)) || err != nil {
				t.Errorf("Copy() = (%d, %v), want (%d, nil)", n, err, len(v.data))
			}
			if err := wr.Close(); err != nil {
				t.Errorf("Close() = %v, want nil", err)
			}

			// Verify that the compression ratio is within expected bounds.
			ratio := float64(len(v.data)) / float64(buf1.Len())
			if ratio < v.ratio {
				t.Errorf("poor compression ratio: %0.2f < %0.2f", ratio, v.ratio)
			}

			// Verify that the C library can decompress the output of Writer and
			// that the Reader can decompress the output of the C library.
			if *zcheck {
				zd, err := cmdDecompress(buf1.Bytes())
				if err != nil {
					t.Errorf("unexpected cmdDecompress error: %v", err)
				}
				if got, want, ok := testutil.Compare(zd, v.data); !ok {
					t.Errorf("output data mismatch:\ngot  %s\nwant %s", got, want)
				}
				zc, err := cmdCompress(v.data)
				if err != nil {
					t.Errorf("unexpected cmdCompress error: %v", err)
				}
				zratio := float64(len(v.data)) / float64(len(zc))
				if ratio < 0.9*zratio {
					t.Errorf("poor compression ratio: %0.2f < %0.2f", ratio, 0.9*zratio)
				}
				buf1.Reset()
				buf1.Write(zc) // Use output of C library for Reader test
			}

			// Decompress the output.
			rd, err := NewReader(&buf1, nil)
			if err != nil {
				t.Errorf("NewReader() = (_, %v), want (_, nil)", err)
			}
			n, err = io.Copy(&buf2, rd)
			if n != int64(len(v.data)) || err != nil {
				t.Errorf("Copy() = (%d, %v), want (%d, nil)", n, err, len(v.data))
			}
			if err := rd.Close(); err != nil {
				t.Errorf("Close() = %v, want nil", err)
			}
			if got, want, ok := testutil.Compare(buf2.Bytes(), v.data); !ok {
				t.Errorf("output data mismatch:\ngot  %s\nwant %s", got, want)
			}
		})
	}
}

func runBenchmarks(b *testing.B, f func(b *testing.B, buf []byte, lvl int)) {
	for _, td := range testdata {
		if len(td.data) == 0 {
			continue
		}
		if testing.Short() && !(td.name == "Twain" || td.name == "Digits") {
			continue
		}
		for _, tl := range levels {
			for _, ts := range sizes {
				buf := testutil.ResizeData(td.data, ts.size)
				b.Run(td.name+"/"+tl.name+"/"+ts.name, func(b *testing.B) {
					f(b, buf, tl.level)
				})
			}
		}
	}
}
<commit_msg>bzip2: trivial argument changes<commit_after>// Copyright 2015, Joe Tsai. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.

package bzip2

import (
	"bytes"
	"errors"
	"flag"
	"io"
	"os/exec"
	"strings"
	"testing"

	"github.com/dsnet/compress/internal/testutil"
)

var zcheck = flag.Bool("zcheck", false, "verify test vectors with C bzip2 library")

func cmdCompress(input []byte) ([]byte, error)   { return cmdExec(input, "-z") }
func cmdDecompress(input []byte) ([]byte, error) { return cmdExec(input, "-d") }

// cmdExec executes the bzip2 tool, passing the input in as stdin.
// It returns the stdout and an error.
func cmdExec(input []byte, args ...string) ([]byte, error) {
	var bo, be bytes.Buffer
	cmd := exec.Command("bzip2", args...)
	cmd.Stdin = bytes.NewReader(input)
	cmd.Stdout = &bo
	cmd.Stderr = &be
	err := cmd.Run()
	ss := strings.Split(strings.TrimSpace(be.String()), "\n")
	if len(ss) > 0 && ss[len(ss)-1] != "" {
		// Assume any stderr indicates an error and last line is the message.
		return nil, errors.New(ss[len(ss)-1])
	}
	return bo.Bytes(), err
}

var testdata = []struct {
	name  string
	data  []byte
	ratio float64 // The minimum expected ratio (uncompressed / compressed)
}{
	{"Nil", nil, 0},
	{"Binary", testutil.MustLoadFile("../testdata/binary.bin"), 5.68},
	{"Digits", testutil.MustLoadFile("../testdata/digits.txt"), 2.22},
	{"Huffman", testutil.MustLoadFile("../testdata/huffman.txt"), 1.24},
	{"Random", testutil.MustLoadFile("../testdata/random.bin"), 0.98},
	{"Repeats", testutil.MustLoadFile("../testdata/repeats.bin"), 3.93},
	{"Twain", testutil.MustLoadFile("../testdata/twain.txt"), 2.99},
	{"Zeros", testutil.MustLoadFile("../testdata/zeros.bin"), 5825.0},
}

var levels = []struct {
	name  string
	level int
}{
	{"Speed", BestSpeed},
	{"Default", DefaultCompression},
	{"Compression", BestCompression},
}

var sizes = []struct {
	name string
	size int
}{
	{"1e4", 1e4},
	{"1e5", 1e5},
	{"1e6", 1e6},
}

func TestRoundTrip(t *testing.T) {
	for _, v := range testdata {
		v := v
		t.Run(v.name, func(t *testing.T) {
			t.Parallel()

			var buf1, buf2 bytes.Buffer

			// Compress the input.
			wr, err := NewWriter(&buf1, nil)
			if err != nil {
				t.Errorf("NewWriter() = (_, %v), want (_, nil)", err)
			}
			n, err := io.Copy(wr, bytes.NewReader(v.data))
			if n != int64(len(v.data)) || err != nil {
				t.Errorf("Copy() = (%d, %v), want (%d, nil)", n, err, len(v.data))
			}
			if err := wr.Close(); err != nil {
				t.Errorf("Close() = %v, want nil", err)
			}

			// Verify that the compression ratio is within expected bounds.
			ratio := float64(len(v.data)) / float64(buf1.Len())
			if ratio < v.ratio {
				t.Errorf("poor compression ratio: %0.2f < %0.2f", ratio, v.ratio)
			}

			// Verify that the C library can decompress the output of Writer and
			// that the Reader can decompress the output of the C library.
			if *zcheck {
				zd, err := cmdDecompress(buf1.Bytes())
				if err != nil {
					t.Errorf("unexpected cmdDecompress error: %v", err)
				}
				if got, want, ok := testutil.Compare(zd, v.data); !ok {
					t.Errorf("output data mismatch:\ngot  %s\nwant %s", got, want)
				}
				zc, err := cmdCompress(v.data)
				if err != nil {
					t.Errorf("unexpected cmdCompress error: %v", err)
				}
				zratio := float64(len(v.data)) / float64(len(zc))
				if ratio < 0.9*zratio {
					t.Errorf("poor compression ratio: %0.2f < %0.2f", ratio, 0.9*zratio)
				}
				buf1.Reset()
				buf1.Write(zc) // Use output of C library for Reader test
			}

			// Decompress the output.
			rd, err := NewReader(&buf1, nil)
			if err != nil {
				t.Errorf("NewReader() = (_, %v), want (_, nil)", err)
			}
			n, err = io.Copy(&buf2, rd)
			if n != int64(len(v.data)) || err != nil {
				t.Errorf("Copy() = (%d, %v), want (%d, nil)", n, err, len(v.data))
			}
			if err := rd.Close(); err != nil {
				t.Errorf("Close() = %v, want nil", err)
			}
			if got, want, ok := testutil.Compare(buf2.Bytes(), v.data); !ok {
				t.Errorf("output data mismatch:\ngot  %s\nwant %s", got, want)
			}
		})
	}
}

func runBenchmarks(b *testing.B, f func(b *testing.B, buf []byte, lvl int)) {
	for _, td := range testdata {
		if len(td.data) == 0 {
			continue
		}
		if testing.Short() && !(td.name == "Twain" || td.name == "Digits") {
			continue
		}
		for _, tl := range levels {
			for _, ts := range sizes {
				buf := testutil.ResizeData(td.data, ts.size)
				b.Run(td.name+"/"+tl.name+"/"+ts.name, func(b *testing.B) {
					f(b, buf, tl.level)
				})
			}
		}
	}
}
<|endoftext|>


<commit_before>package main

// autotest github.com/a8n [paths...] [packages...] [testflags]
//  - new module for log colorization
//  - use StringArray

import (
	"fmt"
	"github.com/go-fsnotify/fsnotify"
	"go/build"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"regexp"
	"syscall"
	"time"
)

type watcher struct {
	// Finished is signaled when the watcher is closed.
	Finished chan bool

	// SettleTime indicates how long to wait after the last file system change before launching.
	SettleTime time.Duration

	// IgnoreDirs lists the names of directories that should not be watched for changes.
	IgnoreDirs map[string]bool

	// IgnoreFiles is a list of regular expression patterns for files that should be ignored.
	IgnoreFiles []*regexp.Regexp

	// TestFlags contains optional arguments for 'go test'.
	TestFlags []string

	debug       bool
	fs          *fsnotify.Watcher
	done        chan bool
	gosrc       string
	paths       []string
	timeSuccess time.Time
	timeFailure time.Time
	lastState   int
}

// Values for lastState
const (
	starting = iota
	working
	failing
)

func round(duration, interval time.Duration) time.Duration {
	var t int64 = int64(duration) + int64(interval)/2
	return time.Duration(t - t%int64(interval))
}

func newWatcher() (*watcher, error) {
	fs, err := fsnotify.NewWatcher()
	if err != nil {
		return nil, err
	}
	self := &watcher{
		Finished:   make(chan bool),
		SettleTime: 2 * time.Second,
		IgnoreDirs: map[string]bool{".git": true},
		IgnoreFiles: []*regexp.Regexp{
			regexp.MustCompile(`\..*\.swp$`),
		},
		TestFlags: make([]string, 0),
		debug:     false,
		fs:        fs,
		done:      make(chan bool),
		gosrc:     filepath.Join(os.Getenv("GOPATH"), "src"),
		paths:     make([]string, 0),
		lastState: starting,
	}
	return self, nil
}

func (self *watcher) Close() error {
	return self.fs.Close()
}

func (self *watcher) Start() {
	go self.monitorChanges()
}

func (self *watcher) Stop() {
	self.done <- true
}

func (self *watcher) Add(path string) error {
	// watch the file system path
	err := self.fs.Add(path)
	if err != nil {
		log.Fatal(err)
	}
	self.paths = append(self.paths, path)

	// is it a package dir (under $GOPATH/src?)
	if pkg := self.getPackageName(path); pkg != "" && self.debug {
		log.Println("package:", pkg, "in path:", path)
	}

	log.Println("watching for changes:", path)
	return err
}

func (self *watcher) Remove(path string) error {
	// find path in self.paths, remove the entry
	for i, val := range self.paths {
		if val == path {
			// delete entry at position i
			copy(self.paths[i:], self.paths[i+1:])
			self.paths = self.paths[0 : len(self.paths)-1]
			break
		}
	}
	return self.fs.Remove(path)
}

// AddRecursive walks a directory recursively, and watches all subdirectories.
func (self *watcher) AddRecursive(path string) error {
	return filepath.Walk(path, func(subpath string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if _, ignore := self.IgnoreDirs[info.Name()]; ignore {
			return filepath.SkipDir
		}
		if info.IsDir() {
			return self.Add(subpath)
		}
		return nil
	})
}

// RunTests invokes the 'go test' tool for all monitored packages.
func (self *watcher) RunTests() {
	if err := self.handleModifications(); err != nil {
		msg := "error: " + err.Error()
		if self.lastState != failing {
			self.timeFailure = time.Now()
		}
		if self.lastState == working {
			msg += fmt.Sprintf(" (%s success)", round(time.Since(self.timeSuccess), time.Second))
		}
		self.lastState = failing
		log.Println("\u001b[31m" + msg + "\u001b[0m")
	} else {
		msg := ""
		if self.lastState != working {
			self.timeSuccess = time.Now()
		}
		if self.lastState == failing {
			msg = fmt.Sprintf("success after %s failures", round(time.Since(self.timeFailure), time.Second))
		}
		self.lastState = working
		if len(msg) != 0 {
			log.Println("\u001b[32m" + msg + "\u001b[0m")
		}
	}
}

// monitorChanges is the main processing loop for file system notifications.
func (self *watcher) monitorChanges() {
	modified := false
	for {
		select {
		case <-self.done:
			self.Finished <- true
			return

		case err := <-self.fs.Errors:
			log.Println("error:", err)

		case event := <-self.fs.Events:
			mod, err := self.handleEvent(event)
			if err != nil {
				log.Println("error:", err)
			} else if mod {
				modified = true
			}

		case <-time.After(self.SettleTime):
			if modified {
				self.RunTests()
				modified = false
			}
		}
	}
}

// handleEvent handles a file system change notification.
func (self *watcher) handleEvent(event fsnotify.Event) (bool, error) {
	filename := event.Name
	modified := false

	if event.Op&fsnotify.Create != 0 {
		info, err := os.Stat(filename)
		if err != nil {
			return false, err
		}
		if info.IsDir() {
			self.Add(filename)
		} else {
			if self.debug {
				log.Println("created:", filename)
			}
			modified = true
		}
	}
	if event.Op&fsnotify.Remove != 0 {
		self.Remove(filename)
		if self.debug {
			log.Println("removed:", filename)
		}
		modified = true
	}
	if event.Op&fsnotify.Write != 0 {
		// skip file if it matches any regexp in IgnoreFiles
		skip := false
		base := filepath.Base(filename)
		for _, re := range self.IgnoreFiles {
			if re.MatchString(base) {
				skip = true
				break
			}
		}
		if skip {
			if self.debug {
				log.Println("skipping:", filename)
			}
		} else {
			if self.debug {
				log.Println("modified:", filename)
			}
			modified = true
		}
	}
	return modified, nil
}

// handleModifications launches 'go test'.
func (self *watcher) handleModifications() error {
	args := make([]string, 1+len(self.TestFlags))
	args[0] = "test"
	copy(args[1:], self.TestFlags)
	npkg := 0
	for _, path := range self.paths {
		if pkg := self.getPackageName(path); pkg != "" {
			args = append(args, pkg)
			npkg++
		}
	}
	cmd := exec.Command("go", args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	log.Printf("running go test with %d packages\n", npkg)
	return cmd.Run()
}

// getPackageName returns the go package name for a path, or "" if not a package dir.
func (self *watcher) getPackageName(path string) string {
	if pkg, err := filepath.Rel(self.gosrc, path); err == nil {
		return pkg
	}
	return ""
}

// --------------------------------------------------------------------------

func getCwd() string {
	cwd, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}
	return cwd
}

// findPackage looks for path in the current directory, and any go source dirs,
// and returns the resolved path or an empty string if not found.
func findPackage(path string) string {
	// check relative to current directory first
	if stat, err := os.Stat(path); err == nil && stat.IsDir() {
		if !filepath.IsAbs(path) {
			path = filepath.Join(getCwd(), path)
		}
		return path
	}

	// check GOROOT / GOPATH
	for _, srcDir := range build.Default.SrcDirs() {
		pkg, err := build.Default.Import(path, srcDir, build.FindOnly)
		if err == nil {
			return pkg.Dir
		}
	}

	log.Println("package not found:", path)
	return ""
}

func main() {
	for _, arg := range os.Args[1:] {
		if arg == "-h" || arg == "--help" {
			fmt.Printf(`Monitors the file system and automatically runs 'go test' on changes.

usage: %s [-h | --help] [testflags] [path...] [package...]

options:
  -h, --help   print this message
  testflags    flags supported by 'go test'; see 'go help testflag'
  path...      filesystem path, monitored recursively
  package...   go package name for which 'go test' will be issued
`, os.Args[0])
			os.Exit(0)
		}
	}
	if os.Getenv("GOPATH") == "" {
		log.Fatalln("GOPATH is not set")
	}

	w, err := newWatcher()
	if err != nil {
		log.Fatal(err)
	}
	w.SettleTime = 500 * time.Millisecond

	// signals used to stop
	signals := make(chan os.Signal, 1)
	signal.Notify(signals, os.Interrupt, os.Kill, syscall.SIGTERM)
	go func() {
		//signal := <-signals
		//log.Println("got signal:", signal)
		<-signals
		w.Stop()
	}()

	// monitor paths
	gotOne := false
	for _, arg := range os.Args[1:] {
		if arg[0] == '-' {
			w.TestFlags = append(w.TestFlags, arg)
		} else if path := findPackage(arg); path != "" {
			if err := w.AddRecursive(path); err != nil {
				log.Fatal(err)
			} else {
				gotOne = true
			}
		}
	}

	if !gotOne {
		log.Fatalln("no paths to watch")
	}

	w.Start()
	w.RunTests()
	<-w.Finished
	w.Close()

	log.Println("exiting")
}
<commit_msg>Add comment about round() method<commit_after>package main

// autotest github.com/a8n [paths...] [packages...] [testflags]
//  - new module for log colorization
//  - use StringArray

import (
	"fmt"
	"github.com/go-fsnotify/fsnotify"
	"go/build"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"regexp"
	"syscall"
	"time"
)

type watcher struct {
	// Finished is signaled when the watcher is closed.
	Finished chan bool

	// SettleTime indicates how long to wait after the last file system change before launching.
	SettleTime time.Duration

	// IgnoreDirs lists the names of directories that should not be watched for changes.
	IgnoreDirs map[string]bool

	// IgnoreFiles is a list of regular expression patterns for files that should be ignored.
	IgnoreFiles []*regexp.Regexp

	// TestFlags contains optional arguments for 'go test'.
	TestFlags []string

	debug       bool
	fs          *fsnotify.Watcher
	done        chan bool
	gosrc       string
	paths       []string
	timeSuccess time.Time
	timeFailure time.Time
	lastState   int
}

// Values for lastState
const (
	starting = iota
	working
	failing
)

// Go's time package does not provide a method such as:
//  func (d *Duration) Round(Duration)
// So we implement it here.
func round(duration, interval time.Duration) time.Duration {
	var t int64 = int64(duration) + int64(interval)/2
	return time.Duration(t - t%int64(interval))
}

func newWatcher() (*watcher, error) {
	fs, err := fsnotify.NewWatcher()
	if err != nil {
		return nil, err
	}
	self := &watcher{
		Finished:   make(chan bool),
		SettleTime: 2 * time.Second,
		IgnoreDirs: map[string]bool{".git": true},
		IgnoreFiles: []*regexp.Regexp{
			regexp.MustCompile(`\..*\.swp$`),
		},
		TestFlags: make([]string, 0),
		debug:     false,
		fs:        fs,
		done:      make(chan bool),
		gosrc:     filepath.Join(os.Getenv("GOPATH"), "src"),
		paths:     make([]string, 0),
		lastState: starting,
	}
	return self, nil
}

func (self *watcher) Close() error {
	return self.fs.Close()
}

func (self *watcher) Start() {
	go self.monitorChanges()
}

func (self *watcher) Stop() {
	self.done <- true
}

func (self *watcher) Add(path string) error {
	// watch the file system path
	err := self.fs.Add(path)
	if err != nil {
		log.Fatal(err)
	}
	self.paths = append(self.paths, path)

	// is it a package dir (under $GOPATH/src?)
	if pkg := self.getPackageName(path); pkg != "" && self.debug {
		log.Println("package:", pkg, "in path:", path)
	}

	log.Println("watching for changes:", path)
	return err
}

func (self *watcher) Remove(path string) error {
	// find path in self.paths, remove the entry
	for i, val := range self.paths {
		if val == path {
			// delete entry at position i
			copy(self.paths[i:], self.paths[i+1:])
			self.paths = self.paths[0 : len(self.paths)-1]
			break
		}
	}
	return self.fs.Remove(path)
}

// AddRecursive walks a directory recursively, and watches all subdirectories.
func (self *watcher) AddRecursive(path string) error {
	return filepath.Walk(path, func(subpath string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if _, ignore := self.IgnoreDirs[info.Name()]; ignore {
			return filepath.SkipDir
		}
		if info.IsDir() {
			return self.Add(subpath)
		}
		return nil
	})
}

// RunTests invokes the 'go test' tool for all monitored packages.
func (self *watcher) RunTests() {
	if err := self.handleModifications(); err != nil {
		msg := "error: " + err.Error()
		if self.lastState != failing {
			self.timeFailure = time.Now()
		}
		if self.lastState == working {
			msg += fmt.Sprintf(" (%s success)", round(time.Since(self.timeSuccess), time.Second))
		}
		self.lastState = failing
		log.Println("\u001b[31m" + msg + "\u001b[0m")
	} else {
		msg := ""
		if self.lastState != working {
			self.timeSuccess = time.Now()
		}
		if self.lastState == failing {
			msg = fmt.Sprintf("success after %s failures", round(time.Since(self.timeFailure), time.Second))
		}
		self.lastState = working
		if len(msg) != 0 {
			log.Println("\u001b[32m" + msg + "\u001b[0m")
		}
	}
}

// monitorChanges is the main processing loop for file system notifications.
func (self *watcher) monitorChanges() {
	modified := false
	for {
		select {
		case <-self.done:
			self.Finished <- true
			return

		case err := <-self.fs.Errors:
			log.Println("error:", err)

		case event := <-self.fs.Events:
			mod, err := self.handleEvent(event)
			if err != nil {
				log.Println("error:", err)
			} else if mod {
				modified = true
			}

		case <-time.After(self.SettleTime):
			if modified {
				self.RunTests()
				modified = false
			}
		}
	}
}

// handleEvent handles a file system change notification.
func (self *watcher) handleEvent(event fsnotify.Event) (bool, error) {
	filename := event.Name
	modified := false

	if event.Op&fsnotify.Create != 0 {
		info, err := os.Stat(filename)
		if err != nil {
			return false, err
		}
		if info.IsDir() {
			self.Add(filename)
		} else {
			if self.debug {
				log.Println("created:", filename)
			}
			modified = true
		}
	}
	if event.Op&fsnotify.Remove != 0 {
		self.Remove(filename)
		if self.debug {
			log.Println("removed:", filename)
		}
		modified = true
	}
	if event.Op&fsnotify.Write != 0 {
		// skip file if it matches any regexp in IgnoreFiles
		skip := false
		base := filepath.Base(filename)
		for _, re := range self.IgnoreFiles {
			if re.MatchString(base) {
				skip = true
				break
			}
		}
		if skip {
			if self.debug {
				log.Println("skipping:", filename)
			}
		} else {
			if self.debug {
				log.Println("modified:", filename)
			}
			modified = true
		}
	}
	return modified, nil
}

// handleModifications launches 'go test'.
func (self *watcher) handleModifications() error {
	args := make([]string, 1+len(self.TestFlags))
	args[0] = "test"
	copy(args[1:], self.TestFlags)
	npkg := 0
	for _, path := range self.paths {
		if pkg := self.getPackageName(path); pkg != "" {
			args = append(args, pkg)
			npkg++
		}
	}
	cmd := exec.Command("go", args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	log.Printf("running go test with %d packages\n", npkg)
	return cmd.Run()
}

// getPackageName returns the go package name for a path, or "" if not a package dir.
func (self *watcher) getPackageName(path string) string {
	if pkg, err := filepath.Rel(self.gosrc, path); err == nil {
		return pkg
	}
	return ""
}

// --------------------------------------------------------------------------

func getCwd() string {
	cwd, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}
	return cwd
}

// findPackage looks for path in the current directory, and any go source dirs,
// and returns the resolved path or an empty string if not found.
func findPackage(path string) string {
	// check relative to current directory first
	if stat, err := os.Stat(path); err == nil && stat.IsDir() {
		if !filepath.IsAbs(path) {
			path = filepath.Join(getCwd(), path)
		}
		return path
	}

	// check GOROOT / GOPATH
	for _, srcDir := range build.Default.SrcDirs() {
		pkg, err := build.Default.Import(path, srcDir, build.FindOnly)
		if err == nil {
			return pkg.Dir
		}
	}

	log.Println("package not found:", path)
	return ""
}

func main() {
	for _, arg := range os.Args[1:] {
		if arg == "-h" || arg == "--help" {
			fmt.Printf(`Monitors the file system and automatically runs 'go test' on changes.

usage: %s [-h | --help] [testflags] [path...] [package...]

options:
  -h, --help   print this message
  testflags    flags supported by 'go test'; see 'go help testflag'
  path...      filesystem path, monitored recursively
  package...   go package name for which 'go test' will be issued
`, os.Args[0])
			os.Exit(0)
		}
	}
	if os.Getenv("GOPATH") == "" {
		log.Fatalln("GOPATH is not set")
	}

	w, err := newWatcher()
	if err != nil {
		log.Fatal(err)
	}
	w.SettleTime = 500 * time.Millisecond

	// signals used to stop
	signals := make(chan os.Signal, 1)
	signal.Notify(signals, os.Interrupt, os.Kill, syscall.SIGTERM)
	go func() {
		//signal := <-signals
		//log.Println("got signal:", signal)
		<-signals
		w.Stop()
	}()

	// monitor paths
	gotOne := false
	for _, arg := range os.Args[1:] {
		if arg[0] == '-' {
			w.TestFlags = append(w.TestFlags, arg)
		} else if path := findPackage(arg); path != "" {
			if err := w.AddRecursive(path); err != nil {
				log.Fatal(err)
			} else {
				gotOne = true
			}
		}
	}

	if !gotOne {
		log.Fatalln("no paths to watch")
	}

	w.Start()
	w.RunTests()
	<-w.Finished
	w.Close()

	log.Println("exiting")
}
<|endoftext|>


<commit_before>// Author: Liam Stanley <me@liamstanley.io>
// Docs: https://marill.liam.sh/
// Repo: https://github.com/Liamraystanley/marill

package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"strconv"
	"strings"
)

// just setup a global logger, and change output during runtime...

var logf *os.File
var logger *log.Logger

func initLoggerWriter(w io.Writer) {
	logger = log.New(w, "", log.Lshortfile|log.LstdFlags)
	logger.Println("initializing logger")
}

func initLogger() {
	var err error
	if conf.out.logFile != "" && conf.out.printDebug {
		logf, err = os.OpenFile(conf.out.logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
		if err != nil {
			fmt.Printf("error opening log file: %s, %v", conf.out.logFile, err)
			os.Exit(1)
		}

		initLoggerWriter(io.MultiWriter(logf, os.Stdout))
		return
	}

	if conf.out.logFile != "" {
		logf, err = os.OpenFile(conf.out.logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
		if err != nil {
			fmt.Printf("error opening log file: %s, %v", conf.out.logFile, err)
			os.Exit(1)
		}

		initLoggerWriter(logf)
		return
	}

	if conf.out.printDebug {
		initLoggerWriter(os.Stdout)
		return
	}

	initLoggerWriter(ioutil.Discard)
}

func closeLogger() {
	if logf != nil {
		logf.Close()
	}
}

// Color represents an ASCII color sequence for use with prettified output
type Color struct {
	Name string
	ID   int
}

var colors = []*Color{
	{"c", 0}, {"bold", 1}, {"black", 30}, {"red", 31}, {"green", 32}, {"yellow", 33},
	{"blue", 34}, {"magenta", 35}, {"cyan", 36}, {"white", 37}, {"gray", 90},
	{"lightred", 91}, {"lightgreen", 92}, {"lightyellow", 93}, {"lightblue", 94},
	{"lightmagenta", 95}, {"lightcyan", 96}, {"lightgray", 97},
}

// StripColor strips all color {patterns} from input
func StripColor(format *string) {
	for _, clr := range colors {
		*format = strings.Replace(*format, "{"+clr.Name+"}", "", -1)
	}
}

// FmtColor adds (or removes) color output depending on user input
func FmtColor(format *string, shouldStrip bool) {
	if shouldStrip {
		StripColor(format)

		return
	}

	for _, clr := range colors {
		*format = strings.Replace(*format, "{"+clr.Name+"}", "\x1b["+strconv.Itoa(clr.ID)+"m", -1)
	}

	*format = *format + "\x1b[0;m"
}

// Output is the bare out struct for which stdout messages are passed to
type Output struct {
	log    *log.Logger
	buffer []string
}

var out = Output{}

func initOut(w io.Writer) {
	out.log = log.New(w, "", 0)
}

func (o Output) Write(b []byte) (int, error) {
	str := fmt.Sprintf("%s", b)
	o.AddLog(str)

	FmtColor(&str, conf.out.noColors)
	o.log.Print(str)

	return len(b), nil
}

func (o *Output) AddLog(line string) {
	o.buffer = append(o.buffer, line)
}

// Printf interprets []*Color{} escape codes and prints them to stdout
func (o *Output) Printf(format string, a ...interface{}) {
	if conf.out.ignoreStd {
		return
	}

	FmtColor(&format, conf.out.noColors)

	out.log.Printf(format, a...)
	o.AddLog(fmt.Sprintf(format, a...))
}

// Println interprets []*Color{} escape codes and prints them to stdout
func (o *Output) Println(a ...interface{}) {
	if conf.out.ignoreStd {
		return
	}

	str := fmt.Sprint(a...)
	FmtColor(&str, conf.out.noColors)

	out.log.Print(str)
	o.AddLog(str)
}

// Fatalf interprets []*Color{} escape codes and prints them to stdout/logger, and exits
func (o *Output) Fatalf(format string, a ...interface{}) {
	// print to regular stdout
	if !conf.out.ignoreStd {
		str := fmt.Sprintf(fmt.Sprintf("{bold}{red}error:{c} %s\n", format), a...)
		FmtColor(&str, conf.out.noColors)
		out.log.Print(str)
		o.AddLog(str)
	}

	// strip color from format
	StripColor(&format)
	logger.Fatalf("error: "+format, a...)
}

// Fatal interprets []*Color{} escape codes and prints them to stdout, and exits
func (o *Output) Fatal(a ...interface{}) {
	// print to regular stdout
	if !conf.out.ignoreStd {
		str := fmt.Sprintf("{bold}{red}error:{c} %s", fmt.Sprintln(a...))
		FmtColor(&str, conf.out.noColors)
		out.log.Print(str)
		o.AddLog(str)
	}

	str := fmt.Sprintln(a...)

	logger.Fatal("error: " + str)
}
<commit_msg>no newlines on errors<commit_after>// Author: Liam Stanley <me@liamstanley.io>
// Docs: https://marill.liam.sh/
// Repo: https://github.com/Liamraystanley/marill

package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"strconv"
	"strings"
)

// just setup a global logger, and change output during runtime...

var logf *os.File
var logger *log.Logger

func initLoggerWriter(w io.Writer) {
	logger = log.New(w, "", log.Lshortfile|log.LstdFlags)
	logger.Println("initializing logger")
}

func initLogger() {
	var err error
	if conf.out.logFile != "" && conf.out.printDebug {
		logf, err = os.OpenFile(conf.out.logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
		if err != nil {
			fmt.Printf("error opening log file: %s, %v", conf.out.logFile, err)
			os.Exit(1)
		}

		initLoggerWriter(io.MultiWriter(logf, os.Stdout))
		return
	}

	if conf.out.logFile != "" {
		logf, err = os.OpenFile(conf.out.logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
		if err != nil {
			fmt.Printf("error opening log file: %s, %v", conf.out.logFile, err)
			os.Exit(1)
		}

		initLoggerWriter(logf)
		return
	}

	if conf.out.printDebug {
		initLoggerWriter(os.Stdout)
		return
	}

	initLoggerWriter(ioutil.Discard)
}

func closeLogger() {
	if logf != nil {
		logf.Close()
	}
}

// Color represents an ASCII color sequence for use with prettified output
type Color struct {
	Name string
	ID   int
}

var colors = []*Color{
	{"c", 0}, {"bold", 1}, {"black", 30}, {"red", 31}, {"green", 32}, {"yellow", 33},
	{"blue", 34}, {"magenta", 35}, {"cyan", 36}, {"white", 37}, {"gray", 90},
	{"lightred", 91}, {"lightgreen", 92}, {"lightyellow", 93}, {"lightblue", 94},
	{"lightmagenta", 95}, {"lightcyan", 96}, {"lightgray", 97},
}

// StripColor strips all color {patterns} from input
func StripColor(format *string) {
	for _, clr := range colors {
		*format = strings.Replace(*format, "{"+clr.Name+"}", "", -1)
	}
}

// FmtColor adds (or removes) color output depending on user input
func FmtColor(format *string, shouldStrip bool) {
	if shouldStrip {
		StripColor(format)

		return
	}

	for _, clr := range colors {
		*format = strings.Replace(*format, "{"+clr.Name+"}", "\x1b["+strconv.Itoa(clr.ID)+"m", -1)
	}

	*format = *format + "\x1b[0;m"
}

// Output is the bare out struct for which stdout messages are passed to
type Output struct {
	log    *log.Logger
	buffer []string
}

var out = Output{}

func initOut(w io.Writer) {
	out.log = log.New(w, "", 0)
}

func (o Output) Write(b []byte) (int, error) {
	str := fmt.Sprintf("%s", b)
	o.AddLog(str)

	FmtColor(&str, conf.out.noColors)
	o.log.Print(str)

	return len(b), nil
}

func (o *Output) AddLog(line string) {
	o.buffer = append(o.buffer, line)
}

// Printf interprets []*Color{} escape codes and prints them to stdout
func (o *Output) Printf(format string, a ...interface{}) {
	if conf.out.ignoreStd {
		return
	}

	FmtColor(&format, conf.out.noColors)

	out.log.Printf(format, a...)
	o.AddLog(fmt.Sprintf(format, a...))
}

// Println interprets []*Color{} escape codes and prints them to stdout
func (o *Output) Println(a ...interface{}) {
	if conf.out.ignoreStd {
		return
	}

	str := fmt.Sprint(a...)
	FmtColor(&str, conf.out.noColors)

	out.log.Print(str)
	o.AddLog(str)
}

// Fatalf interprets []*Color{} escape codes and prints them to stdout/logger, and exits
func (o *Output) Fatalf(format string, a ...interface{}) {
	// print to regular stdout
	if !conf.out.ignoreStd {
		str := fmt.Sprintf(fmt.Sprintf("{bold}{red}error:{c} %s", format), a...)
		FmtColor(&str, conf.out.noColors)
		out.log.Print(str)
		o.AddLog(str)
	}

	// strip color from format
	StripColor(&format)
	logger.Fatalf("error: "+format, a...)
}

// Fatal interprets []*Color{} escape codes and prints them to stdout, and exits
func (o *Output) Fatal(a ...interface{}) {
	// print to regular stdout
	if !conf.out.ignoreStd {
		str := fmt.Sprintf("{bold}{red}error:{c} %s", fmt.Sprintln(a...))
		FmtColor(&str, conf.out.noColors)
		out.log.Print(str)
		o.AddLog(str)
	}

	str := fmt.Sprintln(a...)

	logger.Fatal("error: " + str)
}
<|endoftext|>


<commit_before>package kiwi

import (
	"fmt"
	"strconv"
	"sync"
	"time"
)

// Logger keeps context and log record. There are many loggers initialized
// in different places of application. Loggers are safe for
// concurrent usage.
type (
	Logger struct {
		sync.RWMutex
		contextSrc map[interface{}]interface{}
		context    map[string]recVal
		pairs      map[string]recVal
	}
	Record interface {
		String() string
		IsQuoted() bool
	}
	recVal struct {
		Val    string
		Type   uint8
		Quoted bool
	}
	fnVal struct { // вычислять при вызове log
		fn func() interface{}
	}
)

// obsoleted by recVal iface
const (
	emptyVal uint8 = iota
	stringVal
	integerVal
	floatVal
	booleanVal
	customVal // for use with `func() string`
)

// NewLogger creates logger instance.
func NewLogger() *Logger {
	return &Logger{
		contextSrc: make(map[interface{}]interface{}),
		context:    make(map[string]recVal),
		pairs:      make(map[string]recVal)}
}

// Log is the most common method for flushing previously added key-val pairs to an output.
// After current record is flushed all pairs removed from a record except contextSrc pairs.
func (l *Logger) Log(keyVals ...interface{}) {
	if len(keyVals) > 0 {
		l.Add(keyVals...)
	}
	l.Lock()

	record := l.pairs
	l.pairs = make(map[string]recVal)
	for key, val := range l.context {
		// pairs override context
		if _, ok := record[key]; !ok {
			record[key] = val
		}
	}
	l.Unlock()
	passRecordToOutput(record)
}

// Add a new key-recVal pairs to the log record. If a key already added then value will be
// updated. If a key already exists in a contextSrc then it will be overriden by a new
// recVal for a current record only. After flushing a record with Log() old context value
// will be restored.
func (l *Logger) Add(keyVals ...interface{}) *Logger {
	var key string
	l.Lock()
	for i, val := range keyVals {
		if i%2 == 0 {
			key = toRecordKey(val)
			continue
		}
		l.pairs[key] = toRecordValue(val)
	}
	// for odd number of key-val pairs just add label without recVal
	if len(keyVals)%2 == 1 {
		l.pairs[key] = recVal{"", emptyVal, false}
	}
	l.Unlock()
	return l
}

// With defines a context for the logger.
func (l *Logger) With(keyVals ...interface{}) *Logger {
	var (
		keySrc interface{}
		key    string
	)
	l.Lock()
	for i, val := range keyVals {
		if i%2 == 0 {
			keySrc = val
			key = toRecordKey(val)
			continue
		}
		l.contextSrc[keySrc] = val
		l.context[key] = toRecordValue(val)
	}
	// for odd number of key-val pairs just add label without recVal
	if len(keyVals)%2 == 1 {
		l.contextSrc[keySrc] = nil
		l.context[key] = recVal{"", emptyVal, false}
	}
	l.Unlock()
	return l
}

// Without drops some keys from a context for the logger.
func (l *Logger) Without(keys ...interface{}) *Logger {
	l.Lock()
	for _, key := range keys {
		if _, ok := l.contextSrc[key]; ok {
			delete(l.contextSrc, key)
			delete(l.context, toRecordKey(key))
		}
	}
	l.Unlock()
	return l
}

// WithTimestamp adds "timestamp" field to a context.
func (l *Logger) WithTimestamp(format string) *Logger {
	l.Lock()
	// TODO think about offer fmt.Stringer here instead of custom func?
	l.contextSrc["timestamp"] = func() string { return time.Now().Format(format) }
	l.context["timestamp"] = recVal{time.Now().Format(format), customVal, true}
	l.Unlock()
	return l
}

// Reset logger values added after last Log() call. It keeps contextSrc untouched.
func (l *Logger) Reset() *Logger {
	l.Lock()
	l.pairs = make(map[string]recVal)
	l.Unlock()
	return l
}

// ResetContext resets the context of the logger.
func (l *Logger) ResetContext() *Logger {
	l.Lock()
	l.contextSrc = make(map[interface{}]interface{})
	l.context = make(map[string]recVal)
	l.Unlock()
	return l
}

// GetContext returns copy of context saved in the logger.
func (l *Logger) GetContext() map[interface{}]interface{} {
	var contextSrc = make(map[interface{}]interface{})
	l.RLock()
	for k, v := range l.contextSrc {
		contextSrc[k] = v
	}
	l.RUnlock()
	return contextSrc
}

// GetRecord returns copy of current set of keys and values prepared for logging
// as strings. With context key-vals included.
// The most of Logger operations return *Logger itself but it made for operations
// chaining only. If you need get log pairs use GelRecord() for it.
func (l *Logger) GetRecord() map[string]string {
	var merged = make(map[string]string)
	l.RLock()
	for k, v := range l.context {
		merged[k] = v.Val
	}
	for k, v := range l.pairs {
		merged[k] = v.Val
	}
	l.RUnlock()
	return merged
}

func toRecordKey(val interface{}) string {
	if val == nil {
		return ""
	}
	switch val.(type) {
	case bool:
		if val.(bool) {
			return "true"
		}
		return "false"
	case int:
		return strconv.Itoa(val.(int))
	case int8:
		return strconv.FormatInt(int64(val.(int8)), 10)
	case int16:
		return strconv.FormatInt(int64(val.(int16)), 10)
	case int32:
		return strconv.FormatInt(int64(val.(int32)), 10)
	case int64:
		return strconv.FormatInt(int64(val.(int64)), 10)
	case uint8:
		return strconv.FormatUint(uint64(val.(uint8)), 10)
	case uint16:
		return strconv.FormatUint(uint64(val.(uint16)), 10)
	case uint32:
		return strconv.FormatUint(uint64(val.(uint32)), 10)
	case uint64:
		return strconv.FormatUint(uint64(val.(uint64)), 10)
	case fmt.Stringer:
		return val.(fmt.Stringer).String()
	default:
		return fmt.Sprintf("%v", val)
	}
}

func toRecordValue(val interface{}) recVal {
	if val == nil {
		return recVal{"", emptyVal, false}
	}
	switch val.(type) {
	case bool:
		if val.(bool) {
			return recVal{"true", booleanVal, false}
		}
		return recVal{"false", booleanVal, false}
	case int:
		return recVal{strconv.Itoa(val.(int)), integerVal, false}
	case int8:
		return recVal{strconv.FormatInt(int64(val.(int8)), 10), integerVal, false}
	case int16:
		return recVal{strconv.FormatInt(int64(val.(int16)), 10), integerVal, false}
	case int32:
		return recVal{strconv.FormatInt(int64(val.(int32)), 10), integerVal, false}
	case int64:
		return recVal{strconv.FormatInt(int64(val.(int64)), 10), integerVal, false}
	case uint8:
		return recVal{strconv.FormatUint(uint64(val.(uint8)), 10), integerVal, false}
	case uint16:
		return recVal{strconv.FormatUint(uint64(val.(uint16)), 10), integerVal, false}
	case uint32:
		return recVal{strconv.FormatUint(uint64(val.(uint32)), 10), integerVal, false}
	case uint64:
		return recVal{strconv.FormatUint(uint64(val.(uint64)), 10), integerVal, false}
	case Record:
		return recVal{val.(Record).String(), stringVal, true}
	case fmt.Stringer:
		return recVal{val.(fmt.Stringer).String(), stringVal, true}
	default:
		return recVal{fmt.Sprintf("%v", val), stringVal, true}
	}
}
<commit_msg>Explain exported types.<commit_after>package kiwi

import (
	"fmt"
	"strconv"
	"sync"
	"time"
)

type (
	// Logger keeps context and log record. There are many loggers initialized
	// in different places of application. Loggers are safe for
	// concurrent usage.
	Logger struct {
		sync.RWMutex
		contextSrc map[interface{}]interface{}
		context    map[string]recVal
		pairs      map[string]recVal
	}
	// Record allows log data from any custom types in they conform this interface.
	// Also types that conform fmt.Stringer can be used. But as they not have IsQuoted() check
	// they always treated as strings and displayed in quotes.
	Record interface {
		String() string
		IsQuoted() bool
	}
	recVal struct {
		Val    string
		Type   uint8
		Quoted bool
	}
)

// obsoleted by recVal iface
const (
	emptyVal uint8 = iota
	stringVal
	integerVal
	floatVal
	booleanVal
	customVal // for use with `func() string`
)

// NewLogger creates logger instance.
func NewLogger() *Logger {
	return &Logger{
		contextSrc: make(map[interface{}]interface{}),
		context:    make(map[string]recVal),
		pairs:      make(map[string]recVal)}
}

// Log is the most common method for flushing previously added key-val pairs to an output.
// After current record is flushed all pairs removed from a record except contextSrc pairs.
func (l *Logger) Log(keyVals ...interface{}) {
	if len(keyVals) > 0 {
		l.Add(keyVals...)
	}
	l.Lock()

	record := l.pairs
	l.pairs = make(map[string]recVal)
	for key, val := range l.context {
		// pairs override context
		if _, ok := record[key]; !ok {
			record[key] = val
		}
	}
	l.Unlock()
	passRecordToOutput(record)
}

// Add a new key-recVal pairs to the log record. If a key already added then value will be
// updated. If a key already exists in a contextSrc then it will be overriden by a new
// recVal for a current record only. After flushing a record with Log() old context value
// will be restored.
func (l *Logger) Add(keyVals ...interface{}) *Logger {
	var key string
	l.Lock()
	for i, val := range keyVals {
		if i%2 == 0 {
			key = toRecordKey(val)
			continue
		}
		l.pairs[key] = toRecordValue(val)
	}
	// for odd number of key-val pairs just add label without recVal
	if len(keyVals)%2 == 1 {
		l.pairs[key] = recVal{"", emptyVal, false}
	}
	l.Unlock()
	return l
}

// With defines a context for the logger.
func (l *Logger) With(keyVals ...interface{}) *Logger {
	var (
		keySrc interface{}
		key    string
	)
	l.Lock()
	for i, val := range keyVals {
		if i%2 == 0 {
			keySrc = val
			key = toRecordKey(val)
			continue
		}
		l.contextSrc[keySrc] = val
		l.context[key] = toRecordValue(val)
	}
	// for odd number of key-val pairs just add label without recVal
	if len(keyVals)%2 == 1 {
		l.contextSrc[keySrc] = nil
		l.context[key] = recVal{"", emptyVal, false}
	}
	l.Unlock()
	return l
}

// Without drops some keys from a context for the logger.
func (l *Logger) Without(keys ...interface{}) *Logger {
	l.Lock()
	for _, key := range keys {
		if _, ok := l.contextSrc[key]; ok {
			delete(l.contextSrc, key)
			delete(l.context, toRecordKey(key))
		}
	}
	l.Unlock()
	return l
}

// WithTimestamp adds "timestamp" field to a context.
func (l *Logger) WithTimestamp(format string) *Logger {
	l.Lock()
	// TODO think about offer fmt.Stringer here instead of custom func?
	l.contextSrc["timestamp"] = func() string { return time.Now().Format(format) }
	l.context["timestamp"] = recVal{time.Now().Format(format), customVal, true}
	l.Unlock()
	return l
}

// Reset logger values added after last Log() call. It keeps contextSrc untouched.
func (l *Logger) Reset() *Logger {
	l.Lock()
	l.pairs = make(map[string]recVal)
	l.Unlock()
	return l
}

// ResetContext resets the context of the logger.
func (l *Logger) ResetContext() *Logger {
	l.Lock()
	l.contextSrc = make(map[interface{}]interface{})
	l.context = make(map[string]recVal)
	l.Unlock()
	return l
}

// GetContext returns copy of context saved in the logger.
func (l *Logger) GetContext() map[interface{}]interface{} {
	var contextSrc = make(map[interface{}]interface{})
	l.RLock()
	for k, v := range l.contextSrc {
		contextSrc[k] = v
	}
	l.RUnlock()
	return contextSrc
}

// GetRecord returns copy of current set of keys and values prepared for logging
// as strings. With context key-vals included.
// The most of Logger operations return *Logger itself but it made for operations
// chaining only. If you need get log pairs use GelRecord() for it.
func (l *Logger) GetRecord() map[string]string {
	var merged = make(map[string]string)
	l.RLock()
	for k, v := range l.context {
		merged[k] = v.Val
	}
	for k, v := range l.pairs {
		merged[k] = v.Val
	}
	l.RUnlock()
	return merged
}

func toRecordKey(val interface{}) string {
	if val == nil {
		return ""
	}
	switch val.(type) {
	case bool:
		if val.(bool) {
			return "true"
		}
		return "false"
	case int:
		return strconv.Itoa(val.(int))
	case int8:
		return strconv.FormatInt(int64(val.(int8)), 10)
	case int16:
		return strconv.FormatInt(int64(val.(int16)), 10)
	case int32:
		return strconv.FormatInt(int64(val.(int32)), 10)
	case int64:
		return strconv.FormatInt(int64(val.(int64)), 10)
	case uint8:
		return strconv.FormatUint(uint64(val.(uint8)), 10)
	case uint16:
		return strconv.FormatUint(uint64(val.(uint16)), 10)
	case uint32:
		return strconv.FormatUint(uint64(val.(uint32)), 10)
	case uint64:
		return strconv.FormatUint(uint64(val.(uint64)), 10)
	case fmt.Stringer:
		return val.(fmt.Stringer).String()
	default:
		return fmt.Sprintf("%v", val)
	}
}

func toRecordValue(val interface{}) recVal {
	if val == nil {
		return recVal{"", emptyVal, false}
	}
	switch val.(type) {
	case bool:
		if val.(bool) {
			return recVal{"true", booleanVal, false}
		}
		return recVal{"false", booleanVal, false}
	case int:
		return recVal{strconv.Itoa(val.(int)), integerVal, false}
	case int8:
		return recVal{strconv.FormatInt(int64(val.(int8)), 10), integerVal, false}
	case int16:
		return recVal{strconv.FormatInt(int64(val.(int16)), 10), integerVal, false}
	case int32:
		return recVal{strconv.FormatInt(int64(val.(int32)), 10), integerVal, false}
	case int64:
		return recVal{strconv.FormatInt(int64(val.(int64)), 10), integerVal, false}
	case uint8:
		return recVal{strconv.FormatUint(uint64(val.(uint8)), 10), integerVal, false}
	case uint16:
		return recVal{strconv.FormatUint(uint64(val.(uint16)), 10), integerVal, false}
	case uint32:
		return recVal{strconv.FormatUint(uint64(val.(uint32)), 10), integerVal, false}
	case uint64:
		return recVal{strconv.FormatUint(uint64(val.(uint64)), 10), integerVal, false}
	case Record:
		return recVal{val.(Record).String(), stringVal, true}
	case fmt.Stringer:
		return recVal{val.(fmt.Stringer).String(), stringVal, true}
	default:
		return recVal{fmt.Sprintf("%v", val), stringVal, true}
	}
}
<|endoftext|>