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

package os

import (
	"errors"
	"internal/syscall/windows"
	"runtime"
	"sync/atomic"
	"syscall"
	"time"
)

func (p *Process) wait() (ps *ProcessState, err error) {
	handle := atomic.LoadUintptr(&p.handle)
	s, e := syscall.WaitForSingleObject(syscall.Handle(handle), syscall.INFINITE)
	switch s {
	case syscall.WAIT_OBJECT_0:
		break
	case syscall.WAIT_FAILED:
		return nil, NewSyscallError("WaitForSingleObject", e)
	default:
		return nil, errors.New("os: unexpected result from WaitForSingleObject")
	}
	var ec uint32
	e = syscall.GetExitCodeProcess(syscall.Handle(handle), &ec)
	if e != nil {
		return nil, NewSyscallError("GetExitCodeProcess", e)
	}
	var u syscall.Rusage
	e = syscall.GetProcessTimes(syscall.Handle(handle), &u.CreationTime, &u.ExitTime, &u.KernelTime, &u.UserTime)
	if e != nil {
		return nil, NewSyscallError("GetProcessTimes", e)
	}
	p.setDone()
	// NOTE(brainman): It seems that sometimes process is not dead
	// when WaitForSingleObject returns. But we do not know any
	// other way to wait for it. Sleeping for a while seems to do
	// the trick sometimes.
	// See https://golang.org/issue/25965 for details.
	defer time.Sleep(5 * time.Millisecond)
	defer p.Release()
	return &ProcessState{p.Pid, syscall.WaitStatus{ExitCode: ec}, &u}, nil
}

func terminateProcess(pid, exitcode int) error {
	h, e := syscall.OpenProcess(syscall.PROCESS_TERMINATE, false, uint32(pid))
	if e != nil {
		return NewSyscallError("OpenProcess", e)
	}
	defer syscall.CloseHandle(h)
	e = syscall.TerminateProcess(h, uint32(exitcode))
	return NewSyscallError("TerminateProcess", e)
}

func (p *Process) signal(sig Signal) error {
	handle := atomic.LoadUintptr(&p.handle)
	if handle == uintptr(syscall.InvalidHandle) {
		return syscall.EINVAL
	}
	if p.done() {
		return ErrProcessDone
	}
	if sig == Kill {
		err := terminateProcess(p.Pid, 1)
		runtime.KeepAlive(p)
		return err
	}
	// TODO(rsc): Handle Interrupt too?
	return syscall.Errno(syscall.EWINDOWS)
}

func (p *Process) release() error {
	handle := atomic.LoadUintptr(&p.handle)
	if handle == uintptr(syscall.InvalidHandle) {
		return syscall.EINVAL
	}
	e := syscall.CloseHandle(syscall.Handle(handle))
	if e != nil {
		return NewSyscallError("CloseHandle", e)
	}
	atomic.StoreUintptr(&p.handle, uintptr(syscall.InvalidHandle))
	// no need for a finalizer anymore
	runtime.SetFinalizer(p, nil)
	return nil
}

func findProcess(pid int) (p *Process, err error) {
	const da = syscall.STANDARD_RIGHTS_READ |
		syscall.PROCESS_QUERY_INFORMATION | syscall.SYNCHRONIZE
	h, e := syscall.OpenProcess(da, false, uint32(pid))
	if e != nil {
		return nil, NewSyscallError("OpenProcess", e)
	}
	return newProcess(pid, uintptr(h)), nil
}

func init() {
	cmd := windows.UTF16PtrToString(syscall.GetCommandLine())
	if len(cmd) == 0 {
		arg0, _ := Executable()
		Args = []string{arg0}
	} else {
		Args = commandLineToArgv(cmd)
	}
}

// appendBSBytes appends n '\\' bytes to b and returns the resulting slice.
func appendBSBytes(b []byte, n int) []byte {
	for ; n > 0; n-- {
		b = append(b, '\\')
	}
	return b
}

// readNextArg splits command line string cmd into next
// argument and command line remainder.
func readNextArg(cmd string) (arg []byte, rest string) {
	var b []byte
	var inquote bool
	var nslash int
	for ; len(cmd) > 0; cmd = cmd[1:] {
		c := cmd[0]
		switch c {
		case ' ', '\t':
			if !inquote {
				return appendBSBytes(b, nslash), cmd[1:]
			}
		case '"':
			b = appendBSBytes(b, nslash/2)
			if nslash%2 == 0 {
				// use "Prior to 2008" rule from
				// http://daviddeley.com/autohotkey/parameters/parameters.htm
				// section 5.2 to deal with double double quotes
				if inquote && len(cmd) > 1 && cmd[1] == '"' {
					b = append(b, c)
					cmd = cmd[1:]
				}
				inquote = !inquote
			} else {
				b = append(b, c)
			}
			nslash = 0
			continue
		case '\\':
			nslash++
			continue
		}
		b = appendBSBytes(b, nslash)
		nslash = 0
		b = append(b, c)
	}
	return appendBSBytes(b, nslash), ""
}

// commandLineToArgv splits a command line into individual argument
// strings, following the Windows conventions documented
// at http://daviddeley.com/autohotkey/parameters/parameters.htm#WINARGV
func commandLineToArgv(cmd string) []string {
	var args []string
	for len(cmd) > 0 {
		if cmd[0] == ' ' || cmd[0] == '\t' {
			cmd = cmd[1:]
			continue
		}
		var arg []byte
		arg, cmd = readNextArg(cmd)
		args = append(args, string(arg))
	}
	return args
}

func ftToDuration(ft *syscall.Filetime) time.Duration {
	n := int64(ft.HighDateTime)<<32 + int64(ft.LowDateTime) // in 100-nanosecond intervals
	return time.Duration(n*100) * time.Nanosecond
}

func (p *ProcessState) userTime() time.Duration {
	return ftToDuration(&p.rusage.UserTime)
}

func (p *ProcessState) systemTime() time.Duration {
	return ftToDuration(&p.rusage.KernelTime)
}
<commit_msg>os: terminate windows processes via handle directly<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package os

import (
	"errors"
	"internal/syscall/windows"
	"runtime"
	"sync/atomic"
	"syscall"
	"time"
)

func (p *Process) wait() (ps *ProcessState, err error) {
	handle := atomic.LoadUintptr(&p.handle)
	s, e := syscall.WaitForSingleObject(syscall.Handle(handle), syscall.INFINITE)
	switch s {
	case syscall.WAIT_OBJECT_0:
		break
	case syscall.WAIT_FAILED:
		return nil, NewSyscallError("WaitForSingleObject", e)
	default:
		return nil, errors.New("os: unexpected result from WaitForSingleObject")
	}
	var ec uint32
	e = syscall.GetExitCodeProcess(syscall.Handle(handle), &ec)
	if e != nil {
		return nil, NewSyscallError("GetExitCodeProcess", e)
	}
	var u syscall.Rusage
	e = syscall.GetProcessTimes(syscall.Handle(handle), &u.CreationTime, &u.ExitTime, &u.KernelTime, &u.UserTime)
	if e != nil {
		return nil, NewSyscallError("GetProcessTimes", e)
	}
	p.setDone()
	// NOTE(brainman): It seems that sometimes process is not dead
	// when WaitForSingleObject returns. But we do not know any
	// other way to wait for it. Sleeping for a while seems to do
	// the trick sometimes.
	// See https://golang.org/issue/25965 for details.
	defer time.Sleep(5 * time.Millisecond)
	defer p.Release()
	return &ProcessState{p.Pid, syscall.WaitStatus{ExitCode: ec}, &u}, nil
}

func (p *Process) signal(sig Signal) error {
	handle := atomic.LoadUintptr(&p.handle)
	if handle == uintptr(syscall.InvalidHandle) {
		return syscall.EINVAL
	}
	if p.done() {
		return ErrProcessDone
	}
	if sig == Kill {
		var terminationHandle syscall.Handle
		e := syscall.DuplicateHandle(^syscall.Handle(0), syscall.Handle(handle), ^syscall.Handle(0), &terminationHandle, syscall.PROCESS_TERMINATE, false, 0)
		if e != nil {
			return NewSyscallError("DuplicateHandle", e)
		}
		runtime.KeepAlive(p)
		defer syscall.CloseHandle(terminationHandle)
		e = syscall.TerminateProcess(syscall.Handle(terminationHandle), 1)
		return NewSyscallError("TerminateProcess", e)
	}
	// TODO(rsc): Handle Interrupt too?
	return syscall.Errno(syscall.EWINDOWS)
}

func (p *Process) release() error {
	handle := atomic.LoadUintptr(&p.handle)
	if handle == uintptr(syscall.InvalidHandle) {
		return syscall.EINVAL
	}
	e := syscall.CloseHandle(syscall.Handle(handle))
	if e != nil {
		return NewSyscallError("CloseHandle", e)
	}
	atomic.StoreUintptr(&p.handle, uintptr(syscall.InvalidHandle))
	// no need for a finalizer anymore
	runtime.SetFinalizer(p, nil)
	return nil
}

func findProcess(pid int) (p *Process, err error) {
	const da = syscall.STANDARD_RIGHTS_READ |
		syscall.PROCESS_QUERY_INFORMATION | syscall.SYNCHRONIZE
	h, e := syscall.OpenProcess(da, false, uint32(pid))
	if e != nil {
		return nil, NewSyscallError("OpenProcess", e)
	}
	return newProcess(pid, uintptr(h)), nil
}

func init() {
	cmd := windows.UTF16PtrToString(syscall.GetCommandLine())
	if len(cmd) == 0 {
		arg0, _ := Executable()
		Args = []string{arg0}
	} else {
		Args = commandLineToArgv(cmd)
	}
}

// appendBSBytes appends n '\\' bytes to b and returns the resulting slice.
func appendBSBytes(b []byte, n int) []byte {
	for ; n > 0; n-- {
		b = append(b, '\\')
	}
	return b
}

// readNextArg splits command line string cmd into next
// argument and command line remainder.
func readNextArg(cmd string) (arg []byte, rest string) {
	var b []byte
	var inquote bool
	var nslash int
	for ; len(cmd) > 0; cmd = cmd[1:] {
		c := cmd[0]
		switch c {
		case ' ', '\t':
			if !inquote {
				return appendBSBytes(b, nslash), cmd[1:]
			}
		case '"':
			b = appendBSBytes(b, nslash/2)
			if nslash%2 == 0 {
				// use "Prior to 2008" rule from
				// http://daviddeley.com/autohotkey/parameters/parameters.htm
				// section 5.2 to deal with double double quotes
				if inquote && len(cmd) > 1 && cmd[1] == '"' {
					b = append(b, c)
					cmd = cmd[1:]
				}
				inquote = !inquote
			} else {
				b = append(b, c)
			}
			nslash = 0
			continue
		case '\\':
			nslash++
			continue
		}
		b = appendBSBytes(b, nslash)
		nslash = 0
		b = append(b, c)
	}
	return appendBSBytes(b, nslash), ""
}

// commandLineToArgv splits a command line into individual argument
// strings, following the Windows conventions documented
// at http://daviddeley.com/autohotkey/parameters/parameters.htm#WINARGV
func commandLineToArgv(cmd string) []string {
	var args []string
	for len(cmd) > 0 {
		if cmd[0] == ' ' || cmd[0] == '\t' {
			cmd = cmd[1:]
			continue
		}
		var arg []byte
		arg, cmd = readNextArg(cmd)
		args = append(args, string(arg))
	}
	return args
}

func ftToDuration(ft *syscall.Filetime) time.Duration {
	n := int64(ft.HighDateTime)<<32 + int64(ft.LowDateTime) // in 100-nanosecond intervals
	return time.Duration(n*100) * time.Nanosecond
}

func (p *ProcessState) userTime() time.Duration {
	return ftToDuration(&p.rusage.UserTime)
}

func (p *ProcessState) systemTime() time.Duration {
	return ftToDuration(&p.rusage.KernelTime)
}
<|endoftext|>


<commit_before>// +build !windows

package main

import (
	"github.com/docker/docker/pkg/integration/checker"
	"github.com/go-check/check"
)

func (s *DockerSuite) TestInspectOomKilledTrue(c *check.C) {
	testRequires(c, DaemonIsLinux, memoryLimitSupport)

	name := "testoomkilled"
	_, exitCode, _ := dockerCmdWithError("run", "--name", name, "-m", "10MB", "busybox", "sh", "-c", "x=a; while true; do x=$x$x$x$x; done")

	c.Assert(exitCode, checker.Equals, 137, check.Commentf("OOM exit should be 137"))

	oomKilled, err := inspectField(name, "State.OOMKilled")
	c.Assert(oomKilled, checker.Equals, "true")
	c.Assert(err, checker.IsNil)
}

func (s *DockerSuite) TestInspectOomKilledFalse(c *check.C) {
	testRequires(c, DaemonIsLinux, memoryLimitSupport)

	name := "testoomkilled"
	dockerCmd(c, "run", "--name", name, "-m", "10MB", "busybox", "sh", "-c", "echo hello world")

	oomKilled, err := inspectField(name, "State.OOMKilled")
	c.Assert(oomKilled, checker.Equals, "false")
	c.Assert(err, checker.IsNil)
}
<commit_msg>Increase integration cli test memory<commit_after>// +build !windows

package main

import (
	"github.com/docker/docker/pkg/integration/checker"
	"github.com/go-check/check"
)

func (s *DockerSuite) TestInspectOomKilledTrue(c *check.C) {
	testRequires(c, DaemonIsLinux, memoryLimitSupport)

	name := "testoomkilled"
	_, exitCode, _ := dockerCmdWithError("run", "--name", name, "--memory", "32MB", "busybox", "sh", "-c", "x=a; while true; do x=$x$x$x$x; done")

	c.Assert(exitCode, checker.Equals, 137, check.Commentf("OOM exit should be 137"))

	oomKilled, err := inspectField(name, "State.OOMKilled")
	c.Assert(oomKilled, checker.Equals, "true")
	c.Assert(err, checker.IsNil)
}

func (s *DockerSuite) TestInspectOomKilledFalse(c *check.C) {
	testRequires(c, DaemonIsLinux, memoryLimitSupport)

	name := "testoomkilled"
	dockerCmd(c, "run", "--name", name, "--memory", "32MB", "busybox", "sh", "-c", "echo hello world")

	oomKilled, err := inspectField(name, "State.OOMKilled")
	c.Assert(oomKilled, checker.Equals, "false")
	c.Assert(err, checker.IsNil)
}
<|endoftext|>


<commit_before>package scipipe

import (
	"errors"
	str "strings"
)

// ExecMode specifies which execution mode should be used for a SciProcess and
// its corresponding SciTasks
type ExecMode int

const (
	// ExecModeLocal indicates that commands on the local computer
	ExecModeLocal ExecMode = iota
	// ExecModeSLURM indicates that commands should be executed on a HPC cluster
	// via a SLURM resource manager
	ExecModeSLURM ExecMode = iota
	// ExecModeK8s indicates that commands should be executed on a Kubernetes
	// cluster
	ExecModeK8s ExecMode = iota
)

// ================== Process ==================

// Base interface for all processes
type Process interface {
	IsConnected() bool // Sanity check, to see whether all ports are connected
	Run()
}

// ================== SciProcess ==================

type SciProcess struct {
	Process
	Name             string
	CommandPattern   string
	ExecMode         ExecMode
	Prepend          string
	Spawn            bool
	In               map[string]*FilePort
	Out              map[string]*FilePort
	OutPortsDoStream map[string]bool
	PathFormatters   map[string]func(*SciTask) string
	ParamPorts       map[string]*ParamPort
	CustomExecute    func(*SciTask)
}

func NewSciProcess(name string, command string) *SciProcess {
	return &SciProcess{
		Name:             name,
		CommandPattern:   command,
		In:               make(map[string]*FilePort),
		Out:              make(map[string]*FilePort),
		OutPortsDoStream: make(map[string]bool),
		PathFormatters:   make(map[string]func(*SciTask) string),
		ParamPorts:       make(map[string]*ParamPort),
		Spawn:            true,
	}
}

// ----------- Main API init methods ------------

func NewFromShell(name string, cmd string) *SciProcess {
	if !LogExists {
		InitLogAudit()
	}
	p := NewSciProcess(name, cmd)
	p.initPortsFromCmdPattern(cmd, nil)
	return p
}

func ShellExpand(name string, cmd string, inPaths map[string]string, outPaths map[string]string, params map[string]string) *SciProcess {
	cmdExpr := expandCommandParamsAndPaths(cmd, params, inPaths, outPaths)
	p := NewSciProcess(name, cmdExpr)
	p.initPortsFromCmdPattern(cmdExpr, params)
	return p
}

func (p *SciProcess) GetInPort(portName string) *FilePort {
	if p.In[portName] != nil {
		return p.In[portName]
	} else {
		Error.Printf("No such in-port ('%s') for process '%s'. Please check your workflow code!\n", portName, p.Name)
	}
	return nil
}

func (p *SciProcess) GetOutPort(portName string) *FilePort {
	if p.Out[portName] != nil {
		return p.Out[portName]
	} else {
		Error.Printf("No such out-port ('%s') for process '%s'. Please check your workflow code!\n", portName, p.Name)
	}
	return nil
}

// ----------- Path formatting methods ------------

// SetPathStatic creates an (output) path formatter returning a static string file name
func (p *SciProcess) SetPathStatic(outPortName string, path string) {
	p.PathFormatters[outPortName] = func(t *SciTask) string {
		return path
	}
}

// SetPathExtend creates an (output) path formatter that extends the path of
// an input InformationPacket
func (p *SciProcess) SetPathExtend(inPortName string, outPortName string, extension string) {
	p.PathFormatters[outPortName] = func(t *SciTask) string {
		return t.InTargets[inPortName].GetPath() + extension
	}
}

// SetPathReplace creates an (output) path formatter that uses an input's path
// but replaces parts of it.
func (p *SciProcess) SetPathReplace(inPortName string, outPortName string, old string, new string) {
	p.PathFormatters[outPortName] = func(t *SciTask) string {
		return str.Replace(t.InTargets[inPortName].GetPath(), old, new, -1)
	}
}

// ------- Helper methods for initialization -------

func expandCommandParamsAndPaths(cmd string, params map[string]string, inPaths map[string]string, outPaths map[string]string) (cmdExpr string) {
	r := getShellCommandPlaceHolderRegex()
	ms := r.FindAllStringSubmatch(cmd, -1)
	if params != nil {
		Debug.Println("Params:", params)
	}
	if inPaths != nil {
		Debug.Println("inPaths:", inPaths)
	}
	if outPaths != nil {
		Debug.Println("outPaths:", outPaths)
	}
	cmdExpr = cmd
	Debug.Println("Got command: ", cmd)
	for _, m := range ms {
		placeHolderStr := m[0]
		typ := m[1]
		name := m[2]
		var filePath string
		if typ == "p" {
			if params != nil {
				if val, ok := params[name]; ok {
					Debug.Println("Found param:", val)
					filePath = val
					Debug.Println("Replacing:", placeHolderStr, "->", filePath)
					cmdExpr = str.Replace(cmdExpr, placeHolderStr, filePath, -1)
				}
			}
		} else if typ == "i" {
			if inPaths != nil {
				if val, ok := inPaths[name]; ok {
					Debug.Println("Found inPath:", val)
					filePath = val
					Debug.Println("Replacing:", placeHolderStr, "->", filePath)
					cmdExpr = str.Replace(cmdExpr, placeHolderStr, filePath, -1)
				}
			}
		} else if typ == "o" || typ == "os" {
			if outPaths != nil {
				if val, ok := outPaths[name]; ok {
					Debug.Println("Found outPath:", val)
					filePath = val
					Debug.Println("Replacing:", placeHolderStr, "->", filePath)
					cmdExpr = str.Replace(cmdExpr, placeHolderStr, filePath, -1)
				}
			}
		}
	}
	if cmd != cmdExpr {
		Debug.Printf("Expanded command '%s' into '%s'\n", cmd, cmdExpr)
	}
	return
}

// Set up in- and out-ports based on the shell command pattern used to create the
// SciProcess. Ports are set up in this way:
// `{i:PORTNAME}` specifies an in-port
// `{o:PORTNAME}` specifies an out-port
// `{os:PORTNAME}` specifies an out-port that streams via a FIFO file
// `{p:PORTNAME}` a "parameter-port", which means a port where parameters can be "streamed"
func (p *SciProcess) initPortsFromCmdPattern(cmd string, params map[string]string) {

	// Find in/out port names and Params and set up in struct fields
	r := getShellCommandPlaceHolderRegex()
	ms := r.FindAllStringSubmatch(cmd, -1)
	Debug.Printf("Got the following matches for placeholders in the command, when initializing ports: %v\n", ms)

	for _, m := range ms {
		if len(m) < 3 {
			msg := "Too few matches"
			Check(errors.New(msg), msg)
		}

		typ := m[1]
		name := m[2]
		if typ == "o" || typ == "os" {
			p.Out[name] = NewFilePort()
			if typ == "os" {
				p.OutPortsDoStream[name] = true
			}
		} else if typ == "i" {
			// Set up a channel on the inports, even though this is
			// often replaced by another processes output port channel.
			// It might be nice to have it init'ed with a channel
			// anyways, for use cases when we want to send InformationPacket
			// on the inport manually.
			p.In[name] = NewFilePort()
		} else if typ == "p" {
			if params == nil || params[name] == "" {
				p.ParamPorts[name] = NewParamPort()
			}
		}
	}
}

// ------- Sanity checks -------
func (proc *SciProcess) IsConnected() (isConnected bool) {
	isConnected = true
	for portName, port := range proc.In {
		if !port.IsConnected() {
			Error.Printf("InPort %s of process %s is not connected - check your workflow code!\n", portName, proc.Name)
			isConnected = false
		}
	}
	for portName, port := range proc.Out {
		if !port.IsConnected() {
			Error.Printf("OutPort %s of process %s is not connected - check your workflow code!\n", portName, proc.Name)
			isConnected = false
		}
	}
	for portName, port := range proc.ParamPorts {
		if !port.IsConnected() {
			Error.Printf("ParamPort %s of process %s is not connected - check your workflow code!\n", portName, proc.Name)
			isConnected = false
		}
	}
	return isConnected
}

// ============== SciProcess Run Method ===============

// Run runs the process by instantiating and executing SciTasks for all inputs
// and parameter values on its in-ports. in the case when there are no inputs
// or parameter values on the in-ports, it will run just once before it
// terminates. note that the actual execution of shell commands are done inside
// SciTask.Execute, not here.
func (p *SciProcess) Run() {
	defer p.closeOutPorts()

	tasks := []*SciTask{}
	Debug.Printf("Process %s: Starting to create and schedule tasks\n", p.Name)
	for t := range p.createTasks() {
		// Collect created tasks, for the second round
		// where tasks are waited for to finish, before
		// sending their outputs.
		Debug.Printf("Process %s: Instantiated task [%s] ...", p.Name, t.Command)
		tasks = append(tasks, t)

		anyPreviousFifosExists := t.anyFifosExist()

		if p.ExecMode == ExecModeLocal {
			if !anyPreviousFifosExists {
				Debug.Printf("Process %s: No FIFOs existed, so creating, for task [%s] ...", p.Name, t.Command)
				t.createFifos()
			}

			// Sending FIFOs for the task
			for oname, oip := range t.OutTargets {
				if oip.doStream {
					Debug.Printf("Process %s: Sending FIFO target on outport '%s' for task [%s] ...\n", p.Name, oname, t.Command)
					p.Out[oname].Chan <- oip
				}
			}
		}

		if !anyPreviousFifosExists {
			Debug.Printf("Process %s: Go-Executing task in separate go-routine: [%s] ...\n", p.Name, t.Command)
			// Run the task
			go t.Execute()
			Debug.Printf("Process %s: Done go-executing task in go-routine: [%s] ...\n", p.Name, t.Command)
		} else {
			// Since t.Execute() is not run, that normally sends the Done signal, we
			// have to send it manually here:
			go func() {
				defer close(t.Done)
				t.Done <- 1
			}()
		}
	}

	Debug.Printf("Process %s: Starting to loop over %d tasks to send out targets ...\n", p.Name, len(tasks))
	for _, t := range tasks {
		Debug.Printf("Process %s: Waiting for Done from task: [%s]\n", p.Name, t.Command)
		<-t.Done
		Debug.Printf("Process %s: Received Done from task: [%s]\n", p.Name, t.Command)
		for oname, oip := range t.OutTargets {
			if !oip.doStream {
				Debug.Printf("Process %s: Sending target on outport %s, for task [%s] ...\n", p.Name, oname, t.Command)
				p.Out[oname].Chan <- oip
				Debug.Printf("Process %s: Done sending target on outport %s, for task [%s] ...\n", p.Name, oname, t.Command)
			}
		}
	}
}

// -------- Helper methods for the Run method ---------

func (p *SciProcess) receiveInputs() (inTargets map[string]*InformationPacket, inPortsOpen bool) {
	inPortsOpen = true
	inTargets = make(map[string]*InformationPacket)
	// Read input targets on in-ports and set up path mappings
	for inpName, inPort := range p.In {
		Debug.Printf("Process %s: Receieving on inPort %s ...", p.Name, inpName)
		inTarget, open := <-inPort.Chan
		if !open {
			inPortsOpen = false
			continue
		}
		Debug.Printf("Process %s: Got inTarget %s ...", p.Name, inTarget.GetPath())
		inTargets[inpName] = inTarget
	}
	return
}

func (p *SciProcess) receiveParams() (params map[string]string, paramPortsOpen bool) {
	paramPortsOpen = true
	params = make(map[string]string)
	// Read input targets on in-ports and set up path mappings
	for pname, pport := range p.ParamPorts {
		pval, open := <-pport.Chan
		if !open {
			paramPortsOpen = false
			continue
		}
		Debug.Println("Receiving param:", pname, "with value", pval)
		params[pname] = pval
	}
	return
}

func (p *SciProcess) createTasks() (ch chan *SciTask) {
	ch = make(chan *SciTask)
	go func() {
		defer close(ch)
		for {
			inTargets, inPortsOpen := p.receiveInputs()
			Debug.Printf("Process.createTasks:%s Got inTargets: %v", p.Name, inTargets)
			params, paramPortsOpen := p.receiveParams()
			Debug.Printf("Process.createTasks:%s Got params: %s", p.Name, params)
			if !inPortsOpen && !paramPortsOpen {
				Debug.Printf("Process.createTasks:%s Breaking: Both inPorts and paramPorts closed", p.Name)
				break
			}
			if len(p.In) == 0 && !paramPortsOpen {
				Debug.Printf("Process.createTasks:%s Breaking: No inports, and params closed", p.Name)
				break
			}
			if len(p.ParamPorts) == 0 && !inPortsOpen {
				Debug.Printf("Process.createTasks:%s Breaking: No params, and inPorts closed", p.Name)
				break
			}
			t := NewSciTask(p.Name, p.CommandPattern, inTargets, p.PathFormatters, p.OutPortsDoStream, params, p.Prepend, p.ExecMode)
			if p.CustomExecute != nil {
				t.CustomExecute = p.CustomExecute
			}
			ch <- t
			if len(p.In) == 0 && len(p.ParamPorts) == 0 {
				Debug.Printf("Process.createTasks:%s Breaking: No inports nor params", p.Name)
				break
			}
		}
	}()
	return ch
}

func (p *SciProcess) closeOutPorts() {
	for oname, oport := range p.Out {
		Debug.Printf("Process %s: Closing port %s ...\n", p.Name, oname)
		oport.Close()
	}
}
<commit_msg>Clarify code by switching order of if-statement, and add debug clause, for SciProcess.Run<commit_after>package scipipe

import (
	"errors"
	str "strings"
)

// ExecMode specifies which execution mode should be used for a SciProcess and
// its corresponding SciTasks
type ExecMode int

const (
	// ExecModeLocal indicates that commands on the local computer
	ExecModeLocal ExecMode = iota
	// ExecModeSLURM indicates that commands should be executed on a HPC cluster
	// via a SLURM resource manager
	ExecModeSLURM ExecMode = iota
	// ExecModeK8s indicates that commands should be executed on a Kubernetes
	// cluster
	ExecModeK8s ExecMode = iota
)

// ================== Process ==================

// Base interface for all processes
type Process interface {
	IsConnected() bool // Sanity check, to see whether all ports are connected
	Run()
}

// ================== SciProcess ==================

type SciProcess struct {
	Process
	Name             string
	CommandPattern   string
	ExecMode         ExecMode
	Prepend          string
	Spawn            bool
	In               map[string]*FilePort
	Out              map[string]*FilePort
	OutPortsDoStream map[string]bool
	PathFormatters   map[string]func(*SciTask) string
	ParamPorts       map[string]*ParamPort
	CustomExecute    func(*SciTask)
}

func NewSciProcess(name string, command string) *SciProcess {
	return &SciProcess{
		Name:             name,
		CommandPattern:   command,
		In:               make(map[string]*FilePort),
		Out:              make(map[string]*FilePort),
		OutPortsDoStream: make(map[string]bool),
		PathFormatters:   make(map[string]func(*SciTask) string),
		ParamPorts:       make(map[string]*ParamPort),
		Spawn:            true,
	}
}

// ----------- Main API init methods ------------

func NewFromShell(name string, cmd string) *SciProcess {
	if !LogExists {
		InitLogAudit()
	}
	p := NewSciProcess(name, cmd)
	p.initPortsFromCmdPattern(cmd, nil)
	return p
}

func ShellExpand(name string, cmd string, inPaths map[string]string, outPaths map[string]string, params map[string]string) *SciProcess {
	cmdExpr := expandCommandParamsAndPaths(cmd, params, inPaths, outPaths)
	p := NewSciProcess(name, cmdExpr)
	p.initPortsFromCmdPattern(cmdExpr, params)
	return p
}

func (p *SciProcess) GetInPort(portName string) *FilePort {
	if p.In[portName] != nil {
		return p.In[portName]
	} else {
		Error.Printf("No such in-port ('%s') for process '%s'. Please check your workflow code!\n", portName, p.Name)
	}
	return nil
}

func (p *SciProcess) GetOutPort(portName string) *FilePort {
	if p.Out[portName] != nil {
		return p.Out[portName]
	} else {
		Error.Printf("No such out-port ('%s') for process '%s'. Please check your workflow code!\n", portName, p.Name)
	}
	return nil
}

// ----------- Path formatting methods ------------

// SetPathStatic creates an (output) path formatter returning a static string file name
func (p *SciProcess) SetPathStatic(outPortName string, path string) {
	p.PathFormatters[outPortName] = func(t *SciTask) string {
		return path
	}
}

// SetPathExtend creates an (output) path formatter that extends the path of
// an input InformationPacket
func (p *SciProcess) SetPathExtend(inPortName string, outPortName string, extension string) {
	p.PathFormatters[outPortName] = func(t *SciTask) string {
		return t.InTargets[inPortName].GetPath() + extension
	}
}

// SetPathReplace creates an (output) path formatter that uses an input's path
// but replaces parts of it.
func (p *SciProcess) SetPathReplace(inPortName string, outPortName string, old string, new string) {
	p.PathFormatters[outPortName] = func(t *SciTask) string {
		return str.Replace(t.InTargets[inPortName].GetPath(), old, new, -1)
	}
}

// ------- Helper methods for initialization -------

func expandCommandParamsAndPaths(cmd string, params map[string]string, inPaths map[string]string, outPaths map[string]string) (cmdExpr string) {
	r := getShellCommandPlaceHolderRegex()
	ms := r.FindAllStringSubmatch(cmd, -1)
	if params != nil {
		Debug.Println("Params:", params)
	}
	if inPaths != nil {
		Debug.Println("inPaths:", inPaths)
	}
	if outPaths != nil {
		Debug.Println("outPaths:", outPaths)
	}
	cmdExpr = cmd
	Debug.Println("Got command: ", cmd)
	for _, m := range ms {
		placeHolderStr := m[0]
		typ := m[1]
		name := m[2]
		var filePath string
		if typ == "p" {
			if params != nil {
				if val, ok := params[name]; ok {
					Debug.Println("Found param:", val)
					filePath = val
					Debug.Println("Replacing:", placeHolderStr, "->", filePath)
					cmdExpr = str.Replace(cmdExpr, placeHolderStr, filePath, -1)
				}
			}
		} else if typ == "i" {
			if inPaths != nil {
				if val, ok := inPaths[name]; ok {
					Debug.Println("Found inPath:", val)
					filePath = val
					Debug.Println("Replacing:", placeHolderStr, "->", filePath)
					cmdExpr = str.Replace(cmdExpr, placeHolderStr, filePath, -1)
				}
			}
		} else if typ == "o" || typ == "os" {
			if outPaths != nil {
				if val, ok := outPaths[name]; ok {
					Debug.Println("Found outPath:", val)
					filePath = val
					Debug.Println("Replacing:", placeHolderStr, "->", filePath)
					cmdExpr = str.Replace(cmdExpr, placeHolderStr, filePath, -1)
				}
			}
		}
	}
	if cmd != cmdExpr {
		Debug.Printf("Expanded command '%s' into '%s'\n", cmd, cmdExpr)
	}
	return
}

// Set up in- and out-ports based on the shell command pattern used to create the
// SciProcess. Ports are set up in this way:
// `{i:PORTNAME}` specifies an in-port
// `{o:PORTNAME}` specifies an out-port
// `{os:PORTNAME}` specifies an out-port that streams via a FIFO file
// `{p:PORTNAME}` a "parameter-port", which means a port where parameters can be "streamed"
func (p *SciProcess) initPortsFromCmdPattern(cmd string, params map[string]string) {

	// Find in/out port names and Params and set up in struct fields
	r := getShellCommandPlaceHolderRegex()
	ms := r.FindAllStringSubmatch(cmd, -1)
	Debug.Printf("Got the following matches for placeholders in the command, when initializing ports: %v\n", ms)

	for _, m := range ms {
		if len(m) < 3 {
			msg := "Too few matches"
			Check(errors.New(msg), msg)
		}

		typ := m[1]
		name := m[2]
		if typ == "o" || typ == "os" {
			p.Out[name] = NewFilePort()
			if typ == "os" {
				p.OutPortsDoStream[name] = true
			}
		} else if typ == "i" {
			// Set up a channel on the inports, even though this is
			// often replaced by another processes output port channel.
			// It might be nice to have it init'ed with a channel
			// anyways, for use cases when we want to send InformationPacket
			// on the inport manually.
			p.In[name] = NewFilePort()
		} else if typ == "p" {
			if params == nil || params[name] == "" {
				p.ParamPorts[name] = NewParamPort()
			}
		}
	}
}

// ------- Sanity checks -------
func (proc *SciProcess) IsConnected() (isConnected bool) {
	isConnected = true
	for portName, port := range proc.In {
		if !port.IsConnected() {
			Error.Printf("InPort %s of process %s is not connected - check your workflow code!\n", portName, proc.Name)
			isConnected = false
		}
	}
	for portName, port := range proc.Out {
		if !port.IsConnected() {
			Error.Printf("OutPort %s of process %s is not connected - check your workflow code!\n", portName, proc.Name)
			isConnected = false
		}
	}
	for portName, port := range proc.ParamPorts {
		if !port.IsConnected() {
			Error.Printf("ParamPort %s of process %s is not connected - check your workflow code!\n", portName, proc.Name)
			isConnected = false
		}
	}
	return isConnected
}

// ============== SciProcess Run Method ===============

// Run runs the process by instantiating and executing SciTasks for all inputs
// and parameter values on its in-ports. in the case when there are no inputs
// or parameter values on the in-ports, it will run just once before it
// terminates. note that the actual execution of shell commands are done inside
// SciTask.Execute, not here.
func (p *SciProcess) Run() {
	defer p.closeOutPorts()

	tasks := []*SciTask{}
	Debug.Printf("Process %s: Starting to create and schedule tasks\n", p.Name)
	for t := range p.createTasks() {
		// Collect created tasks, for the second round
		// where tasks are waited for to finish, before
		// sending their outputs.
		Debug.Printf("Process %s: Instantiated task [%s] ...", p.Name, t.Command)
		tasks = append(tasks, t)

		anyPreviousFifosExists := t.anyFifosExist()

		if p.ExecMode == ExecModeLocal {
			if !anyPreviousFifosExists {
				Debug.Printf("Process %s: No FIFOs existed, so creating, for task [%s] ...", p.Name, t.Command)
				t.createFifos()
			}

			// Sending FIFOs for the task
			for oname, oip := range t.OutTargets {
				if oip.doStream {
					Debug.Printf("Process %s: Sending FIFO target on outport '%s' for task [%s] ...\n", p.Name, oname, t.Command)
					p.Out[oname].Chan <- oip
				}
			}
		}

		if anyPreviousFifosExists {
			Debug.Printf("Process %s: Previous FIFOs existed, so not executing task [%s] ...\n", p.Name, t.Command)
			// Since t.Execute() is not run, that normally sends the Done signal, we
			// have to send it manually here:
			go func() {
				defer close(t.Done)
				t.Done <- 1
			}()
		} else {
			Debug.Printf("Process %s: Go-Executing task in separate go-routine: [%s] ...\n", p.Name, t.Command)
			// Run the task
			go t.Execute()
			Debug.Printf("Process %s: Done go-executing task in go-routine: [%s] ...\n", p.Name, t.Command)
		}
	}

	Debug.Printf("Process %s: Starting to loop over %d tasks to send out targets ...\n", p.Name, len(tasks))
	for _, t := range tasks {
		Debug.Printf("Process %s: Waiting for Done from task: [%s]\n", p.Name, t.Command)
		<-t.Done
		Debug.Printf("Process %s: Received Done from task: [%s]\n", p.Name, t.Command)
		for oname, oip := range t.OutTargets {
			if !oip.doStream {
				Debug.Printf("Process %s: Sending target on outport %s, for task [%s] ...\n", p.Name, oname, t.Command)
				p.Out[oname].Chan <- oip
				Debug.Printf("Process %s: Done sending target on outport %s, for task [%s] ...\n", p.Name, oname, t.Command)
			}
		}
	}
}

// -------- Helper methods for the Run method ---------

func (p *SciProcess) receiveInputs() (inTargets map[string]*InformationPacket, inPortsOpen bool) {
	inPortsOpen = true
	inTargets = make(map[string]*InformationPacket)
	// Read input targets on in-ports and set up path mappings
	for inpName, inPort := range p.In {
		Debug.Printf("Process %s: Receieving on inPort %s ...", p.Name, inpName)
		inTarget, open := <-inPort.Chan
		if !open {
			inPortsOpen = false
			continue
		}
		Debug.Printf("Process %s: Got inTarget %s ...", p.Name, inTarget.GetPath())
		inTargets[inpName] = inTarget
	}
	return
}

func (p *SciProcess) receiveParams() (params map[string]string, paramPortsOpen bool) {
	paramPortsOpen = true
	params = make(map[string]string)
	// Read input targets on in-ports and set up path mappings
	for pname, pport := range p.ParamPorts {
		pval, open := <-pport.Chan
		if !open {
			paramPortsOpen = false
			continue
		}
		Debug.Println("Receiving param:", pname, "with value", pval)
		params[pname] = pval
	}
	return
}

func (p *SciProcess) createTasks() (ch chan *SciTask) {
	ch = make(chan *SciTask)
	go func() {
		defer close(ch)
		for {
			inTargets, inPortsOpen := p.receiveInputs()
			Debug.Printf("Process.createTasks:%s Got inTargets: %v", p.Name, inTargets)
			params, paramPortsOpen := p.receiveParams()
			Debug.Printf("Process.createTasks:%s Got params: %s", p.Name, params)
			if !inPortsOpen && !paramPortsOpen {
				Debug.Printf("Process.createTasks:%s Breaking: Both inPorts and paramPorts closed", p.Name)
				break
			}
			if len(p.In) == 0 && !paramPortsOpen {
				Debug.Printf("Process.createTasks:%s Breaking: No inports, and params closed", p.Name)
				break
			}
			if len(p.ParamPorts) == 0 && !inPortsOpen {
				Debug.Printf("Process.createTasks:%s Breaking: No params, and inPorts closed", p.Name)
				break
			}
			t := NewSciTask(p.Name, p.CommandPattern, inTargets, p.PathFormatters, p.OutPortsDoStream, params, p.Prepend, p.ExecMode)
			if p.CustomExecute != nil {
				t.CustomExecute = p.CustomExecute
			}
			ch <- t
			if len(p.In) == 0 && len(p.ParamPorts) == 0 {
				Debug.Printf("Process.createTasks:%s Breaking: No inports nor params", p.Name)
				break
			}
		}
	}()
	return ch
}

func (p *SciProcess) closeOutPorts() {
	for oname, oport := range p.Out {
		Debug.Printf("Process %s: Closing port %s ...\n", p.Name, oname)
		oport.Close()
	}
}
<|endoftext|>


<commit_before>package imgo

import (
	"errors"
)

//input a image matrix as src , return a image matrix by sunseteffect process
func SunsetEffect(src [][][]uint8)(imgMatrix [][][]uint8 , err error) {
	imgMatrix = src

	height:=len(imgMatrix)
	width:=len(imgMatrix[0])
	if height == 0 || width == 0 {
		err = errors.New("The input of matrix is illegal!")
	}

	for i:=0;i<height;i++{
		for j:=0;j<width;j++{
			imgMatrix[i][j][1] = uint8( float64(imgMatrix[i][j][1]) * 0.7 )
			imgMatrix[i][j][2] = uint8( float64(imgMatrix[i][j][2]) * 0.7 )
		}
	}

	return
}

// input a image as src , return a image matrix by negativefilmeffect process
func NegativeFilmEffect(src [][][]uint8)(imgMatrix [][][]uint8 , err error) {
	imgMatrix = src

	height:=len(imgMatrix)
	width:=len(imgMatrix[0])
	if height == 0 || width == 0 {
		err = errors.New("The input of matrix is illegal!")
	}

	for i:=0;i<height;i++{
		for j:=0;j<width;j++{
			imgMatrix[i][j][0] = 255 - imgMatrix[i][j][0]
			imgMatrix[i][j][1] = 255 - imgMatrix[i][j][1]
			imgMatrix[i][j][2] = 255 - imgMatrix[i][j][2]
		}
	}

	return
}

func AdjustBrightness(src [][][]uint8 , light float64)(imgMatrix [][][]uint8 , err error) {
	imgMatrix = src

	if light <= 0{
		err = errors.New("value of light must be more than 0")
		return
	}

	height:=len(imgMatrix)
	width:=len(imgMatrix[0])
	if height == 0 || width == 0 {
		err = errors.New("The input of matrix is illegal!")
	}

	for i:=0;i<height;i++{
		for j:=0;j<width;j++{
			imgMatrix[i][j][0] = uint8(float64(imgMatrix[i][j][0])*light)
			imgMatrix[i][j][1] = uint8(float64(imgMatrix[i][j][1])*light)
			imgMatrix[i][j][2] = uint8(float64(imgMatrix[i][j][2])*light)
		}
	}

	return
}

// fuse two images(filepath) and the size of new image is as src1
func ImageFusion(src1 string , src2 string)(imgMatrix [][][]uint8 , err error) {
	imgMatrix1,err1 := Read(src1)

	if err1 != nil {
		err = err1
		return
	}

	height:=len(imgMatrix1)
	width:=len(imgMatrix1[0])

	imgMatrix2,err2 := ResizeForMatrix(src2,width,height)

	if err2 != nil {
		err = err2
		return
	}

	for i:=0;i<height;i++{
		for j:=0;j<width;j++{
			imgMatrix1[i][j][0] = uint8(float64(imgMatrix1[i][j][0])*0.5)+uint8(float64(imgMatrix2[i][j][0])*0.5)
			imgMatrix1[i][j][1] = uint8(float64(imgMatrix1[i][j][1])*0.5)+uint8(float64(imgMatrix2[i][j][1])*0.5)
			imgMatrix1[i][j][2] = uint8(float64(imgMatrix1[i][j][2])*0.5)+uint8(float64(imgMatrix1[i][j][2])*0.5)
		}
	}
	imgMatrix = imgMatrix1
	return
}

func VerticalMirror(src [][][]uint8)(imgMatrix [][][]uint8 , err error){
	height:=len(src)
	width:=len(src[0])
	if height == 0 || width == 0 {
		err = errors.New("The input of matrix is illegal!")
	}

	newwidth:=width*2
	imgMatrix=NewRGBAMatrix(height,newwidth)

	for i:=0;i<height;i++{
		for j:=0;j<width;j++{
		imgMatrix[i][j] = src[i][j]
		}
	}

	for i:=0;i<height;i++{
		for j:=width;j<newwidth;j++{
			imgMatrix[i][j] = imgMatrix[i][newwidth-j-1]
		}
	}

	return
}

func HorizontalMirror(src [][][]uint8)(imgMatrix [][][]uint8 , err error){
	height:=len(src)
	width:=len(src[0])
	if height == 0 || width == 0 {
		err = errors.New("The input of matrix is illegal!")
	}

	newheight:=height*2
	imgMatrix=NewRGBAMatrix(newheight,width)

	for i:=0;i<height;i++{
		for j:=0;j<width;j++{
		imgMatrix[i][j] = src[i][j]
		}
	}

	for i:=height;i<newheight;i++{
		for j:=0;j<width;j++{
			imgMatrix[i][j] = imgMatrix[newheight-i-1][j]
		}
	}

	return
}

func VerticalMirrorPart(src [][][]uint8)(imgMatrix [][][]uint8 , err error){
	imgMatrix = src

	height:=len(imgMatrix)
	width:=len(imgMatrix[0])
	if height == 0 || width == 0 {
		err = errors.New("The input of matrix is illegal!")
	}

	mirror_w:=width/2

	for i:=0;i<height;i++{
		for j:=0;j<mirror_w;j++{
			imgMatrix[i][j] = imgMatrix[i][width-j-1]
		}
	}

	return
}

//make a mirror of src
func HorizontalMirrorPart(src [][][]uint8)(imgMatrix [][][]uint8 , err error){
	imgMatrix = src

	height:=len(imgMatrix)
	width:=len(imgMatrix[0])
	if height == 0 || width == 0 {
		err = errors.New("The input of matrix is illegal!")
	}

	mirror_h:=height/2

	for i:=0;i<mirror_h;i++{
		for j:=0;j<width;j++{
		imgMatrix[height-i-1][j] = imgMatrix[i][j]
		}
	}

	return
}

func RGB2Gray(src [][][]uint8)(imgMatrix [][][]uint8 , err error){
	imgMatrix = src

	height:=len(imgMatrix)
	width:=len(imgMatrix[0])
	if height == 0 || width == 0 {
		err = errors.New("The input of matrix is illegal!")
	}

	for i:=0;i<height;i++{
		for j:=0;j<width;j++{
		avg:=(imgMatrix[i][j][0]+imgMatrix[i][j][1]+imgMatrix[i][j][3])/3
		imgMatrix[i][j][0] = avg
		imgMatrix[i][j][1] = avg
		imgMatrix[i][j][2] = avg
		}
	}
	return
}

// set the opacity of image matrix , opacity must be 0.0 to 1.0
func SetOpacity(src [][][]uint8, opacity float64)(imgMatrix [][][]uint8 , err error){
	imgMatrix = src

	height:=len(imgMatrix)
	width:=len(imgMatrix[0])
	if height == 0 || width == 0 {
		err = errors.New("The input of matrix is illegal!")
	}

	if opacity < 0.0 || opacity > 1.0 {
		err = errors.New("the opacity is illegal!")
	}

	for i:=0;i<height;i++{
		for j:=0;j<width;j++{
		imgMatrix[i][j][3] = uint8(float64(imgMatrix[i][j][3])*opacity)
		}
	}
	return
}<commit_msg>update<commit_after>package imgo

import (
	"errors"
)

//input a image matrix as src , return a image matrix by sunseteffect process
func SunsetEffect(src [][][]uint8) [][][]uint8 {

	height := len(src)
	width := len(src[0])
	imgMatrix := NewRGBAMatrix(height,width)
	copy(imgMatrix,src)

	for i:=0;i<height;i++{
		for j:=0;j<width;j++{
			imgMatrix[i][j][1] = uint8( float64(imgMatrix[i][j][1]) * 0.7 )
			imgMatrix[i][j][2] = uint8( float64(imgMatrix[i][j][2]) * 0.7 )
		}
	}

	return imgMatrix
}

// input a image as src , return a image matrix by negativefilmeffect process
func NegativeFilmEffect(src [][][]uint8) [][][]uint8 {

	height := len(src)
	width := len(src[0])
	imgMatrix := NewRGBAMatrix(height,width)
	copy(imgMatrix,src)

	for i:=0;i<height;i++{
		for j:=0;j<width;j++{
			imgMatrix[i][j][0] = 255 - imgMatrix[i][j][0]
			imgMatrix[i][j][1] = 255 - imgMatrix[i][j][1]
			imgMatrix[i][j][2] = 255 - imgMatrix[i][j][2]
		}
	}

	return imgMatrix
}

func AdjustBrightness(src [][][]uint8 , light float64)(imgMatrix [][][]uint8 , err error) {

	if light <= 0{
		err = errors.New("value of light must be more than 0")
		return
	}

	height := len(src)
	width := len(src[0])
	imgMatrix = NewRGBAMatrix(height,width)
	copy(imgMatrix,src)

	for i:=0;i<height;i++{
		for j:=0;j<width;j++{
			imgMatrix[i][j][0] = uint8(float64(imgMatrix[i][j][0])*light)
			imgMatrix[i][j][1] = uint8(float64(imgMatrix[i][j][1])*light)
			imgMatrix[i][j][2] = uint8(float64(imgMatrix[i][j][2])*light)
		}
	}

	return
}

// fuse two images(filepath) and the size of new image is as src1
func ImageFusion(src1 string , src2 string)(imgMatrix [][][]uint8 , err error) {
	imgMatrix1,err1 := Read(src1)

	if err1 != nil {
		err = err1
		return
	}

	height:=len(imgMatrix1)
	width:=len(imgMatrix1[0])

	imgMatrix2,err2 := ResizeForMatrix(src2,width,height)

	if err2 != nil {
		err = err2
		return
	}

	for i:=0;i<height;i++{
		for j:=0;j<width;j++{
			imgMatrix1[i][j][0] = uint8(float64(imgMatrix1[i][j][0])*0.5)+uint8(float64(imgMatrix2[i][j][0])*0.5)
			imgMatrix1[i][j][1] = uint8(float64(imgMatrix1[i][j][1])*0.5)+uint8(float64(imgMatrix2[i][j][1])*0.5)
			imgMatrix1[i][j][2] = uint8(float64(imgMatrix1[i][j][2])*0.5)+uint8(float64(imgMatrix1[i][j][2])*0.5)
		}
	}
	imgMatrix = imgMatrix1
	return
}

func VerticalMirror(src [][][]uint8)(imgMatrix [][][]uint8 , err error){
	height := len(src)
	width := len(src[0])
	imgMatrix := NewRGBAMatrix(height,width)
	copy(imgMatrix,src)

	newwidth:=width*2
	imgMatrix=NewRGBAMatrix(height,newwidth)

	for i:=0;i<height;i++{
		for j:=0;j<width;j++{
		imgMatrix[i][j] = src[i][j]
		}
	}

	for i:=0;i<height;i++{
		for j:=width;j<newwidth;j++{
			imgMatrix[i][j] = imgMatrix[i][newwidth-j-1]
		}
	}

	return
}

func HorizontalMirror(src [][][]uint8)(imgMatrix [][][]uint8 , err error){
	height:=len(src)
	width:=len(src[0])
	if height == 0 || width == 0 {
		err = errors.New("The input of matrix is illegal!")
	}

	newheight:=height*2
	imgMatrix=NewRGBAMatrix(newheight,width)

	for i:=0;i<height;i++{
		for j:=0;j<width;j++{
		imgMatrix[i][j] = src[i][j]
		}
	}

	for i:=height;i<newheight;i++{
		for j:=0;j<width;j++{
			imgMatrix[i][j] = imgMatrix[newheight-i-1][j]
		}
	}

	return
}

func VerticalMirrorPart(src [][][]uint8)(imgMatrix [][][]uint8 , err error){
	imgMatrix = src

	height:=len(imgMatrix)
	width:=len(imgMatrix[0])
	if height == 0 || width == 0 {
		err = errors.New("The input of matrix is illegal!")
	}

	mirror_w:=width/2

	for i:=0;i<height;i++{
		for j:=0;j<mirror_w;j++{
			imgMatrix[i][j] = imgMatrix[i][width-j-1]
		}
	}

	return
}

//make a mirror of src
func HorizontalMirrorPart(src [][][]uint8)(imgMatrix [][][]uint8 , err error){
	imgMatrix = src

	height:=len(imgMatrix)
	width:=len(imgMatrix[0])
	if height == 0 || width == 0 {
		err = errors.New("The input of matrix is illegal!")
	}

	mirror_h:=height/2

	for i:=0;i<mirror_h;i++{
		for j:=0;j<width;j++{
		imgMatrix[height-i-1][j] = imgMatrix[i][j]
		}
	}

	return
}

func RGB2Gray(src [][][]uint8)(imgMatrix [][][]uint8 , err error){
	imgMatrix = src

	height:=len(imgMatrix)
	width:=len(imgMatrix[0])
	if height == 0 || width == 0 {
		err = errors.New("The input of matrix is illegal!")
	}

	for i:=0;i<height;i++{
		for j:=0;j<width;j++{
		avg:=(imgMatrix[i][j][0]+imgMatrix[i][j][1]+imgMatrix[i][j][3])/3
		imgMatrix[i][j][0] = avg
		imgMatrix[i][j][1] = avg
		imgMatrix[i][j][2] = avg
		}
	}
	return
}

// set the opacity of image matrix , opacity must be 0.0 to 1.0
func SetOpacity(src [][][]uint8, opacity float64)(imgMatrix [][][]uint8 , err error){
	imgMatrix = src

	height:=len(imgMatrix)
	width:=len(imgMatrix[0])
	if height == 0 || width == 0 {
		err = errors.New("The input of matrix is illegal!")
	}

	if opacity < 0.0 || opacity > 1.0 {
		err = errors.New("the opacity is illegal!")
	}

	for i:=0;i<height;i++{
		for j:=0;j<width;j++{
		imgMatrix[i][j][3] = uint8(float64(imgMatrix[i][j][3])*opacity)
		}
	}
	return
}<|endoftext|>


<commit_before>package queue

import (
	"github.com/nathan-osman/go-cannon/util"

	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"log"
	"net/smtp"
	"net/textproto"
	"os"
	"sync"
	"syscall"
	"time"
)

// Persistent connection to an SMTP host.
type Host struct {
	sync.Mutex
	host         string
	storage      *Storage
	lastActivity time.Time
	newMessage   *util.NonBlockingChan
	stop         chan bool
}

// Log the specified message for the specified host.
func (h *Host) log(msg string) {
	log.Printf("[%s] %s", h.host, msg)
}

// Receive the next message in the queue. The host queue is considered
// "inactive" while waiting for new messages to arrive. The current time is
// recorded before entering the select{} block so that the Idle() method can
// calculate the idle time.
func (h *Host) receiveMessage() *Message {
	h.Lock()
	h.lastActivity = time.Now()
	h.Unlock()
	defer func() {
		h.Lock()
		h.lastActivity = time.Time{}
		h.Unlock()
	}()
	for {
		select {
		case i := <-h.newMessage.Recv:
			return i.(*Message)
		case <-h.stop:
			return nil
		}
	}
}

// Attempt to connect to the specified server. The connection attempt is
// performed in a separate goroutine, allowing it to be aborted if the host
// queue is shut down.
func (h *Host) tryMailServer(server string) (*smtp.Client, error) {
	var (
		c    *smtp.Client
		err  error
		done = make(chan bool)
	)
	go func() {
		c, err = smtp.Dial(fmt.Sprintf("%s:25", server))
		close(done)
	}()
	select {
	case <-done:
	case <-h.stop:
		return nil, nil
	}
	if err == nil {
		if hostname, err := os.Hostname(); err == nil {
			c.Hello(hostname)
		}
		if ok, _ := c.Extension("STARTTLS"); ok {
			if err := c.StartTLS(&tls.Config{ServerName: server}); err != nil {
				return nil, err
			}
		}
		return c, nil
	} else {
		return nil, err
	}
}

// Attempt to connect to one of the mail servers.
func (h *Host) connectToMailServer() (*smtp.Client, error) {
	for _, s := range util.FindMailServers(h.host) {
		if c, err := h.tryMailServer(s); err == nil {
			return c, nil
		} else {
			h.log(fmt.Sprintf("unable to connect to %s", s))
		}
	}
	return nil, errors.New("unable to connect to a mail server")
}

// Attempt to send the specified message to the specified client.
func (h *Host) deliverToMailServer(c *smtp.Client, m *Message) error {
	if r, err := h.storage.GetMessageBody(m); err == nil {
		if err := c.Mail(m.From); err != nil {
			return err
		}
		for _, t := range m.To {
			if err := c.Rcpt(t); err != nil {
				return err
			}
		}
		if w, err := c.Data(); err == nil {
			if _, err := io.Copy(w, r); err != nil {
				return err
			}
			if err := w.Close(); err != nil {
				return err
			}
		} else {
			return err
		}
		return r.Close()
	} else {
		return err
	}
}

// Receive message and deliver them to their recipients. Due to the complicated
// algorithm for message delivery, the body of the method is broken up into a
// sequence of labeled sections.
func (h *Host) run() {
	var (
		m        *Message
		c        *smtp.Client
		err      error
		tries    int
		duration time.Duration
	)
receive:
	if m == nil {
		if m = h.receiveMessage(); m == nil {
			goto shutdown
		}
		h.log("message received in queue")
	}
deliver:
	if c == nil {
		h.log("connecting to mail server...")
		c, err = h.connectToMailServer()
		if c == nil {
			if err != nil {
				h.log(err.Error())
				goto wait
			} else {
				goto shutdown
			}
		}
		h.log("connection established")
	}
	if err := h.deliverToMailServer(c, m); err == nil {
		h.log("mail delivered successfully")
	} else {
		if _, ok := err.(syscall.Errno); ok {
			c = nil
			goto deliver
		} else if e, ok := err.(*textproto.Error); ok {
			if e.Code >= 400 && e.Code <= 499 {
				c.Close()
				c = nil
				goto wait
			} else {
				c.Reset()
			}
		} else {
			h.log(err.Error())

		}
	}
cleanup:
	h.log("deleting message from disk")
	if err := h.storage.DeleteMessage(m); err != nil {
		h.log(err.Error())
	}
	m = nil
	tries = 0
	goto receive
wait:
	tries++
	// We differ a tiny bit from the RFC spec here but this should work well
	// enough - retry once after a minute, twice on the half-hour, and 16 more
	// times every three hours. This is roughly 48 hours.
	switch {
	case tries == 1:
		duration = time.Minute
	case tries < 4:
		duration = 30 * time.Minute
	case tries < 20:
		duration = 3 * time.Hour
	default:
		h.log("maximum retry count exceeded")
		goto cleanup
	}
	select {
	case <-h.stop:
	case <-time.After(duration):
		goto receive
	}
shutdown:
	h.log("shutting down queue")
	if c != nil {
		c.Close()
	}
	close(h.stop)
}

// Create a new host connection.
func NewHost(host string, storage *Storage) *Host {
	h := &Host{
		host:       host,
		storage:    storage,
		newMessage: util.NewNonBlockingChan(),
		stop:       make(chan bool),
	}
	go h.run()
	return h
}

// Attempt to deliver a message to the host.
func (h *Host) Deliver(m *Message) {
	h.newMessage.Send <- m
}

// Retrieve the connection idle time.
func (h *Host) Idle() time.Duration {
	h.Lock()
	defer h.Unlock()
	if h.lastActivity.IsZero() {
		return 0
	} else {
		return time.Since(h.lastActivity)
	}
}

// Close the connection to the host.
func (h *Host) Stop() {
	h.stop <- true
	<-h.stop
}
<commit_msg>Minor improvements to the host queue.<commit_after>package queue

import (
	"github.com/nathan-osman/go-cannon/util"

	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"log"
	"net/smtp"
	"net/textproto"
	"os"
	"sync"
	"syscall"
	"time"
)

// Persistent connection to an SMTP host.
type Host struct {
	sync.Mutex
	host         string
	storage      *Storage
	lastActivity time.Time
	newMessage   *util.NonBlockingChan
	stop         chan bool
}

// Log the specified message for the specified host.
func (h *Host) log(msg string) {
	log.Printf("[%s] %s", h.host, msg)
}

// Receive the next message in the queue. The host queue is considered
// "inactive" while waiting for new messages to arrive. The current time is
// recorded before entering the select{} block so that the Idle() method can
// calculate the idle time.
func (h *Host) receiveMessage() *Message {
	h.Lock()
	h.lastActivity = time.Now()
	h.Unlock()
	defer func() {
		h.Lock()
		h.lastActivity = time.Time{}
		h.Unlock()
	}()
	for {
		select {
		case i := <-h.newMessage.Recv:
			return i.(*Message)
		case <-h.stop:
			return nil
		}
	}
}

// Attempt to connect to the specified server. The connection attempt is
// performed in a separate goroutine, allowing it to be aborted if the host
// queue is shut down.
func (h *Host) tryMailServer(server string) (*smtp.Client, error) {
	var (
		c    *smtp.Client
		err  error
		done = make(chan bool)
	)
	go func() {
		c, err = smtp.Dial(fmt.Sprintf("%s:25", server))
		close(done)
	}()
	select {
	case <-done:
	case <-h.stop:
		return nil, nil
	}
	if err == nil {
		if hostname, err := os.Hostname(); err == nil {
			if err := c.Hello(hostname); err != nil {
				return nil, err
			}
		}
		if ok, _ := c.Extension("STARTTLS"); ok {
			if err := c.StartTLS(&tls.Config{ServerName: server}); err != nil {
				return nil, err
			}
		}
		return c, nil
	} else {
		return nil, err
	}
}

// Attempt to connect to one of the mail servers.
func (h *Host) connectToMailServer() (*smtp.Client, error) {
	for _, s := range util.FindMailServers(h.host) {
		if c, err := h.tryMailServer(s); err == nil {
			return c, nil
		} else {
			h.log(fmt.Sprintf("unable to connect to %s", s))
		}
	}
	return nil, errors.New("unable to connect to a mail server")
}

// Attempt to send the specified message to the specified client.
func (h *Host) deliverToMailServer(c *smtp.Client, m *Message) error {
	if r, err := h.storage.GetMessageBody(m); err == nil {
		if err := c.Mail(m.From); err != nil {
			return err
		}
		for _, t := range m.To {
			if err := c.Rcpt(t); err != nil {
				return err
			}
		}
		if w, err := c.Data(); err == nil {
			if _, err := io.Copy(w, r); err != nil {
				return err
			}
			if err := w.Close(); err != nil {
				return err
			}
		} else {
			return err
		}
		return r.Close()
	} else {
		return err
	}
}

// Receive message and deliver them to their recipients. Due to the complicated
// algorithm for message delivery, the body of the method is broken up into a
// sequence of labeled sections.
func (h *Host) run() {
	var (
		m        *Message
		c        *smtp.Client
		err      error
		tries    int
		duration time.Duration
	)
receive:
	if m == nil {
		if m = h.receiveMessage(); m == nil {
			goto shutdown
		}
		h.log("message received in queue")
	}
deliver:
	if c == nil {
		h.log("connecting to mail server...")
		c, err = h.connectToMailServer()
		if c == nil {
			if err != nil {
				h.log(err.Error())
				goto wait
			} else {
				goto shutdown
			}
		}
		h.log("connection established")
	}
	if err := h.deliverToMailServer(c, m); err == nil {
		h.log("mail delivered successfully")
	} else {
		h.log(err.Error())
		if _, ok := err.(syscall.Errno); ok {
			c = nil
			goto deliver
		} else if e, ok := err.(*textproto.Error); ok {
			if e.Code >= 400 && e.Code <= 499 {
				c.Close()
				c = nil
				goto wait
			} else {
				c.Reset()
			}
		}
	}
cleanup:
	h.log("deleting message from disk")
	if err := h.storage.DeleteMessage(m); err != nil {
		h.log(err.Error())
	}
	m = nil
	tries = 0
	goto receive
wait:
	tries++
	// We differ a tiny bit from the RFC spec here but this should work well
	// enough - retry once after a minute, twice on the half-hour, and 16 more
	// times every three hours. This is roughly 48 hours.
	switch {
	case tries == 1:
		duration = time.Minute
	case tries < 4:
		duration = 30 * time.Minute
	case tries < 20:
		duration = 3 * time.Hour
	default:
		h.log("maximum retry count exceeded")
		goto cleanup
	}
	select {
	case <-h.stop:
	case <-time.After(duration):
		goto receive
	}
shutdown:
	h.log("shutting down queue")
	if c != nil {
		c.Close()
	}
	close(h.stop)
}

// Create a new host connection.
func NewHost(host string, storage *Storage) *Host {
	h := &Host{
		host:       host,
		storage:    storage,
		newMessage: util.NewNonBlockingChan(),
		stop:       make(chan bool),
	}
	go h.run()
	return h
}

// Attempt to deliver a message to the host.
func (h *Host) Deliver(m *Message) {
	h.newMessage.Send <- m
}

// Retrieve the connection idle time.
func (h *Host) Idle() time.Duration {
	h.Lock()
	defer h.Unlock()
	if h.lastActivity.IsZero() {
		return 0
	} else {
		return time.Since(h.lastActivity)
	}
}

// Close the connection to the host.
func (h *Host) Stop() {
	h.stop <- true
	<-h.stop
}
<|endoftext|>


<commit_before>package worker_test

import (
	"encoding/json"

	"github.com/vitalie/worker"
	"golang.org/x/net/context"
)

// MemoryQueue represents an ordered queue,
// this queue is used in unit tests only.
type MemoryQueue struct {
	count uint64
	l     []*worker.Message
}

func NewMemoryQueue() worker.Queue {
	return &MemoryQueue{
		l: []*worker.Message{},
	}
}

func (q *MemoryQueue) Put(ctx context.Context, j worker.Job) error {
	typ, err := worker.StructType(j)
	if err != nil {
		return err
	}

	job := &worker.Envelope{
		Type: typ,
		Args: j,
	}

	body, err := json.Marshal(job)
	if err != nil {
		return err
	}

	q.count++
	msg, err := worker.NewMessage(q.count, body)
	if err != nil {
		return err
	}
	q.l = append(q.l, msg)

	return nil
}

func (q *MemoryQueue) Get(ctx context.Context) (*worker.Message, error) {
	if len(q.l) == 0 {
		return nil, &worker.Error{Err: "timeout", IsTimeout: true}
	}

	var m *worker.Message
	m, q.l = q.l[len(q.l)-1], q.l[:len(q.l)-1]
	return m, nil
}

func (q *MemoryQueue) Delete(ctx context.Context, msg *worker.Message) error {
	var lst []*worker.Message
	for _, m := range q.l {
		if m.ID != msg.ID {
			lst = append(lst, m)
		}
	}
	return nil
}

func (q *MemoryQueue) Reject(ctx context.Context, msg *worker.Message) error {
	return q.Delete(ctx, msg)
}

func (q *MemoryQueue) Size(ctx context.Context) (uint64, error) {
	size := len(q.l)
	return uint64(size), nil
}
<commit_msg>Use mutex to gain access<commit_after>package worker_test

import (
	"encoding/json"
	"sync"

	"github.com/vitalie/worker"
	"golang.org/x/net/context"
)

// MemoryQueue represents an ordered queue,
// this queue is used in unit tests only.
type MemoryQueue struct {
	sync.Mutex
	count uint64
	l     []*worker.Message
}

func NewMemoryQueue() worker.Queue {
	return &MemoryQueue{
		l: []*worker.Message{},
	}
}

func (q *MemoryQueue) Put(ctx context.Context, j worker.Job) error {
	q.Lock()
	defer q.Unlock()

	typ, err := worker.StructType(j)
	if err != nil {
		return err
	}

	job := &worker.Envelope{
		Type: typ,
		Args: j,
	}

	body, err := json.Marshal(job)
	if err != nil {
		return err
	}

	q.count++
	msg, err := worker.NewMessage(q.count, body)
	if err != nil {
		return err
	}
	q.l = append(q.l, msg)

	return nil
}

func (q *MemoryQueue) Get(ctx context.Context) (*worker.Message, error) {
	q.Lock()
	defer q.Unlock()

	if len(q.l) == 0 {
		return nil, &worker.Error{Err: "timeout", IsTimeout: true}
	}

	var m *worker.Message
	m, q.l = q.l[len(q.l)-1], q.l[:len(q.l)-1]
	return m, nil
}

func (q *MemoryQueue) Delete(ctx context.Context, msg *worker.Message) error {
	q.Lock()
	defer q.Unlock()

	var lst []*worker.Message
	for _, m := range q.l {
		if m.ID != msg.ID {
			lst = append(lst, m)
		}
	}
	return nil
}

func (q *MemoryQueue) Reject(ctx context.Context, msg *worker.Message) error {
	return q.Delete(ctx, msg)
}

func (q *MemoryQueue) Size(ctx context.Context) (uint64, error) {
	q.Lock()
	defer q.Unlock()

	size := len(q.l)
	return uint64(size), nil
}
<|endoftext|>


<commit_before>package command

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"net/http/httptest"
	"sort"
	"sync"
	"testing"
	"time"

	"github.com/mackerelio/golib/logging"
	"github.com/mackerelio/mackerel-agent/agent"
	"github.com/mackerelio/mackerel-agent/checks"
	"github.com/mackerelio/mackerel-agent/config"
	"github.com/mackerelio/mackerel-agent/mackerel"
	"github.com/mackerelio/mackerel-agent/metrics"
)

func TestDelayByHost(t *testing.T) {
	delay1 := time.Duration(delayByHost(&mackerel.Host{
		ID:     "246PUVUngPo",
		Name:   "hogehoge2.host.h",
		Type:   "unknown",
		Status: "working",
	})) * time.Second

	delay2 := time.Duration(delayByHost(&mackerel.Host{
		ID:     "21GZjCE5Etb",
		Name:   "hogehoge2.host.h",
		Type:   "unknown",
		Status: "working",
	})) * time.Second

	if !(0 <= delay1.Seconds() && delay1.Seconds() < 60) {
		t.Errorf("delay shoud be between 0 and 60 but %v", delay1)
	}

	if delay1 == delay2 {
		t.Error("delays shoud be different")
	}
}

type jsonObject map[string]interface{}

// newMockAPIServer makes a dummy root directry, a mock API server, a conf.Config to using them
// and returns the Config, mock handlers map and the server.
// The mock handlers map is "<method> <path>"-to-jsonObject-generator map.
func newMockAPIServer(t *testing.T) (config.Config, map[string]func(*http.Request) (int, jsonObject), *httptest.Server) {
	mockHandlers := map[string]func(*http.Request) (int, jsonObject){}

	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		key := req.Method + " " + req.URL.Path
		handler, ok := mockHandlers[key]
		if !ok {
			t.Fatal("Unexpected request: " + key)
		}

		statusCode, data := handler(req)

		respJSON, err := json.Marshal(data)
		if err != nil {
			t.Fatal("marshalling JSON failed: ", err)
		}

		if statusCode != 0 {
			w.WriteHeader(statusCode)
		}

		w.Header().Set("Content-Type", "application/json")
		fmt.Fprint(w, string(respJSON))
	}))

	root, err := ioutil.TempDir("", "mackerel-agent-test")
	if err != nil {
		t.Fatal(err)
	}

	conf := config.Config{
		Apibase:    ts.URL,
		Root:       root,
		Connection: config.DefaultConfig.Connection,
	}

	return conf, mockHandlers, ts
}

func TestPrepareWithCreate(t *testing.T) {
	conf, mockHandlers, ts := newMockAPIServer(t)
	defer ts.Close()

	mockHandlers["POST /api/v0/hosts"] = func(req *http.Request) (int, jsonObject) {
		return 200, jsonObject{
			"id": "xxx1234567890",
		}
	}

	mockHandlers["GET /api/v0/hosts/xxx1234567890"] = func(req *http.Request) (int, jsonObject) {
		return 200, jsonObject{
			"host": mackerel.Host{
				ID:     "xxx1234567890",
				Name:   "host.example.com",
				Type:   "unknown",
				Status: "standby",
			},
		}
	}

	c, _ := Prepare(&conf, &AgentMeta{})
	api := c.API
	host := c.Host

	if api.BaseURL.String() != ts.URL {
		t.Errorf("Apibase mismatch: %s != %s", api.BaseURL, ts.URL)
	}

	if host.ID != "xxx1234567890" {
		t.Error("Host ID mismatch", host)
	}

	if host.Name != "host.example.com" {
		t.Error("Host name mismatch", host)
	}
}

func TestPrepareWithCreateWithFail(t *testing.T) {
	conf, mockHandlers, ts := newMockAPIServer(t)
	defer ts.Close()

	mockHandlers["POST /api/v0/hosts"] = func(req *http.Request) (int, jsonObject) {
		return 403, jsonObject{
			"result": "error",
		}
	}

	origRetryNum := retryNum
	retryNum = 1
	defer func() {
		retryNum = origRetryNum
	}()

	_, err := Prepare(&conf, &AgentMeta{})

	if err == nil {
		t.Errorf("error should be occurred")
	}
}

func TestPrepareWithUpdate(t *testing.T) {
	conf, mockHandlers, ts := newMockAPIServer(t)
	defer ts.Close()
	tempDir, _ := ioutil.TempDir("", "")
	conf.Root = tempDir
	conf.SaveHostID("xxx12345678901")

	mockHandlers["PUT /api/v0/hosts/xxx12345678901"] = func(req *http.Request) (int, jsonObject) {
		return 200, jsonObject{
			"result": "OK",
		}
	}

	mockHandlers["GET /api/v0/hosts/xxx12345678901"] = func(req *http.Request) (int, jsonObject) {
		return 200, jsonObject{
			"host": mackerel.Host{
				ID:     "xxx12345678901",
				Name:   "host.example.com",
				Type:   "unknown",
				Status: "standby",
			},
		}
	}

	c, _ := Prepare(&conf, &AgentMeta{})
	api := c.API
	host := c.Host

	if api.BaseURL.String() != ts.URL {
		t.Errorf("Apibase mismatch: %s != %s", api.BaseURL, ts.URL)
	}

	if host.ID != "xxx12345678901" {
		t.Error("Host ID mismatch", host)
	}

	if host.Name != "host.example.com" {
		t.Error("Host name mismatch", host)
	}
}

func TestCollectHostSpecs(t *testing.T) {
	conf := config.Config{}
	hostname, meta, _ /*interfaces*/, _ /*customIdentifier*/, err := collectHostSpecs(&conf)

	if err != nil {
		t.Errorf("collectHostSpecs should not fail: %s", err)
	}

	if hostname == "" {
		t.Error("hostname should not be empty")
	}

	if _, ok := meta["cpu"]; !ok {
		t.Error("meta.cpu should exist")
	}
}

type counterGenerator struct {
	counter int
	sync.Mutex
}

func (g *counterGenerator) Generate() (metrics.Values, error) {
	g.Lock()
	defer g.Unlock()
	g.counter = g.counter + 1
	return map[string]float64{"dummy.a": float64(g.counter)}, nil
}

type byTime []mackerel.CreatingMetricsValue

func (b byTime) Len() int           { return len(b) }
func (b byTime) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }
func (b byTime) Less(i, j int) bool { return b[i].Time < b[j].Time }

func TestLoop(t *testing.T) {
	if testing.Verbose() {
		logging.SetLogLevel(logging.DEBUG)
	}

	conf, mockHandlers, ts := newMockAPIServer(t)
	defer ts.Close()

	if testing.Short() {
		// Shrink time scale
		originalPostMetricsInterval := config.PostMetricsInterval

		config.PostMetricsInterval = 10 * time.Second
		ratio := config.PostMetricsInterval.Seconds() / originalPostMetricsInterval.Seconds()

		conf.Connection.PostMetricsDequeueDelaySeconds =
			int(float64(config.DefaultConfig.Connection.PostMetricsRetryDelaySeconds) * ratio)
		conf.Connection.PostMetricsRetryDelaySeconds =
			int(float64(config.DefaultConfig.Connection.PostMetricsRetryDelaySeconds) * ratio)

		defer func() {
			config.PostMetricsInterval = originalPostMetricsInterval
		}()
	}

	/// Simulate the situation that mackerel.io is down for 3 min
	// Strategy:
	// counterGenerator generates values 1,2,3,4,...
	// when we got value 3, the server will start responding 503 for three times (inclusive)
	// so the agent should queue the generated values and retry sending.
	//
	//  status: o . o . x . x . x . o o o o o
	//    send: 1 . 2 . 3 . 3 . 3 . 3 4 5 6 7
	// collect: 1 . 2 . 3 . 4 . 5 . 6 . 7 . 8
	//           ^
	//           30s
	const (
		totalFailures = 3
		totalPosts    = 7
	)
	failureCount := 0
	receivedDataPoints := []mackerel.CreatingMetricsValue{}
	done := make(chan struct{})

	mockHandlers["POST /api/v0/tsdb"] = func(req *http.Request) (int, jsonObject) {
		payload := []mackerel.CreatingMetricsValue{}
		json.NewDecoder(req.Body).Decode(&payload)

		for _, p := range payload {
			value := p.Value.(float64)
			if value == 3 {
				failureCount++
				if failureCount <= totalFailures {
					return 503, jsonObject{
						"failure": failureCount, // just for DEBUG logging
					}
				}
			}

			if value == totalPosts {
				defer func() { done <- struct{}{} }()
			}
		}

		receivedDataPoints = append(receivedDataPoints, payload...)

		return 200, jsonObject{
			"success": true,
		}
	}
	mockHandlers["PUT /api/v0/hosts/xyzabc12345"] = func(req *http.Request) (int, jsonObject) {
		return 200, jsonObject{
			"result": "OK",
		}
	}

	// Prepare required objects...
	ag := &agent.Agent{
		MetricsGenerators: []metrics.Generator{
			&counterGenerator{},
		},
	}

	api, err := mackerel.NewAPI(conf.Apibase, conf.Apikey, true)
	if err != nil {
		t.Fatal(err)
	}

	host := &mackerel.Host{ID: "xyzabc12345"}

	termCh := make(chan struct{})
	exitCh := make(chan error)
	app := &App{
		Agent:     ag,
		Config:    &conf,
		API:       api,
		Host:      host,
		AgentMeta: &AgentMeta{},
	}
	// Start looping!
	go func() {
		exitCh <- loop(app, termCh)
	}()

	<-done

	// Verify results
	if len(receivedDataPoints) != totalPosts {
		t.Errorf("the agent should have sent %d datapoints, got: %+v", totalPosts, receivedDataPoints)
	}

	sort.Sort(byTime(receivedDataPoints))

	for i := 0; i < totalPosts; i++ {
		value := receivedDataPoints[i].Value.(float64)
		if value != float64(i+1) {
			t.Errorf("the %dth datapoint should have value %d, got: %+v", i, i+1, receivedDataPoints)
		}
	}

	termCh <- struct{}{}
	exitErr := <-exitCh
	if exitErr != nil {
		t.Errorf("exitErr should be nil, got: %s", exitErr)
	}
}

func TestReportCheckMonitors(t *testing.T) {
	if testing.Verbose() {
		logging.SetLogLevel(logging.DEBUG)
	}

	conf, mockHandlers, ts := newMockAPIServer(t)
	defer ts.Close()

	if testing.Short() {
		conf.Connection.ReportCheckRetryDelaySeconds = 1
	}

	var unexpectedReports []checks.Report
	totalPosts := rand.New(rand.NewSource(time.Now().UnixNano())).Intn(3) + 3
	postCount := 0
	done := make(chan struct{})

	now := time.Now()

	reports := []*checks.Report{
		&checks.Report{Name: "check_test", Status: checks.StatusOK, OccurredAt: now},
		&checks.Report{Name: "check_test", Status: checks.StatusCritical, OccurredAt: now.Add(1 * time.Second)},
		&checks.Report{Name: "check_test", Status: checks.StatusOK, OccurredAt: now.Add(2 * time.Second)},
	}

	mockHandlers["POST /api/v0/monitoring/checks/report"] = func(req *http.Request) (int, jsonObject) {
		payload := []checks.Report{}
		json.NewDecoder(req.Body).Decode(&payload)

		for i, v := range payload {
			if v.Name != reports[i].Name || v.Status != reports[i].Status || v.OccurredAt != now.Add(time.Duration(i)*time.Second) {
				unexpectedReports = append(unexpectedReports, v)
				continue
			}
		}

		postCount++

		if postCount == totalPosts {
			defer func() { done <- struct{}{} }()
		}

		return 503, jsonObject{
			"failure": len(payload),
		}
	}

	api, err := mackerel.NewAPI(conf.Apibase, conf.Apikey, true)
	if err != nil {
		t.Fatal(err)
	}

	host := &mackerel.Host{ID: "xyzabc12345"}

	app := &App{
		Agent:     &agent.Agent{},
		Config:    &conf,
		API:       api,
		Host:      host,
		AgentMeta: &AgentMeta{},
	}

	go func() {
		reportCheckMonitors(app, reports)
	}()

	<-done

	if len(unexpectedReports) != 0 {
		t.Errorf("the agent should have resend same reports. got %+v", unexpectedReports)
	}
}
<commit_msg>fix test<commit_after>package command

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"sort"
	"sync"
	"testing"
	"time"

	"github.com/mackerelio/golib/logging"
	"github.com/mackerelio/mackerel-agent/agent"
	"github.com/mackerelio/mackerel-agent/checks"
	"github.com/mackerelio/mackerel-agent/config"
	"github.com/mackerelio/mackerel-agent/mackerel"
	"github.com/mackerelio/mackerel-agent/metrics"
)

func TestDelayByHost(t *testing.T) {
	delay1 := time.Duration(delayByHost(&mackerel.Host{
		ID:     "246PUVUngPo",
		Name:   "hogehoge2.host.h",
		Type:   "unknown",
		Status: "working",
	})) * time.Second

	delay2 := time.Duration(delayByHost(&mackerel.Host{
		ID:     "21GZjCE5Etb",
		Name:   "hogehoge2.host.h",
		Type:   "unknown",
		Status: "working",
	})) * time.Second

	if !(0 <= delay1.Seconds() && delay1.Seconds() < 60) {
		t.Errorf("delay shoud be between 0 and 60 but %v", delay1)
	}

	if delay1 == delay2 {
		t.Error("delays shoud be different")
	}
}

type jsonObject map[string]interface{}

// newMockAPIServer makes a dummy root directry, a mock API server, a conf.Config to using them
// and returns the Config, mock handlers map and the server.
// The mock handlers map is "<method> <path>"-to-jsonObject-generator map.
func newMockAPIServer(t *testing.T) (config.Config, map[string]func(*http.Request) (int, jsonObject), *httptest.Server) {
	mockHandlers := map[string]func(*http.Request) (int, jsonObject){}

	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		key := req.Method + " " + req.URL.Path
		handler, ok := mockHandlers[key]
		if !ok {
			t.Fatal("Unexpected request: " + key)
		}

		statusCode, data := handler(req)

		respJSON, err := json.Marshal(data)
		if err != nil {
			t.Fatal("marshalling JSON failed: ", err)
		}

		if statusCode != 0 {
			w.WriteHeader(statusCode)
		}

		w.Header().Set("Content-Type", "application/json")
		fmt.Fprint(w, string(respJSON))
	}))

	root, err := ioutil.TempDir("", "mackerel-agent-test")
	if err != nil {
		t.Fatal(err)
	}

	conf := config.Config{
		Apibase:    ts.URL,
		Root:       root,
		Connection: config.DefaultConfig.Connection,
	}

	return conf, mockHandlers, ts
}

func TestPrepareWithCreate(t *testing.T) {
	conf, mockHandlers, ts := newMockAPIServer(t)
	defer ts.Close()

	mockHandlers["POST /api/v0/hosts"] = func(req *http.Request) (int, jsonObject) {
		return 200, jsonObject{
			"id": "xxx1234567890",
		}
	}

	mockHandlers["GET /api/v0/hosts/xxx1234567890"] = func(req *http.Request) (int, jsonObject) {
		return 200, jsonObject{
			"host": mackerel.Host{
				ID:     "xxx1234567890",
				Name:   "host.example.com",
				Type:   "unknown",
				Status: "standby",
			},
		}
	}

	c, _ := Prepare(&conf, &AgentMeta{})
	api := c.API
	host := c.Host

	if api.BaseURL.String() != ts.URL {
		t.Errorf("Apibase mismatch: %s != %s", api.BaseURL, ts.URL)
	}

	if host.ID != "xxx1234567890" {
		t.Error("Host ID mismatch", host)
	}

	if host.Name != "host.example.com" {
		t.Error("Host name mismatch", host)
	}
}

func TestPrepareWithCreateWithFail(t *testing.T) {
	conf, mockHandlers, ts := newMockAPIServer(t)
	defer ts.Close()

	mockHandlers["POST /api/v0/hosts"] = func(req *http.Request) (int, jsonObject) {
		return 403, jsonObject{
			"result": "error",
		}
	}

	origRetryNum := retryNum
	retryNum = 1
	defer func() {
		retryNum = origRetryNum
	}()

	_, err := Prepare(&conf, &AgentMeta{})

	if err == nil {
		t.Errorf("error should be occurred")
	}
}

func TestPrepareWithUpdate(t *testing.T) {
	conf, mockHandlers, ts := newMockAPIServer(t)
	defer ts.Close()
	tempDir, _ := ioutil.TempDir("", "")
	conf.Root = tempDir
	conf.SaveHostID("xxx12345678901")

	mockHandlers["PUT /api/v0/hosts/xxx12345678901"] = func(req *http.Request) (int, jsonObject) {
		return 200, jsonObject{
			"result": "OK",
		}
	}

	mockHandlers["GET /api/v0/hosts/xxx12345678901"] = func(req *http.Request) (int, jsonObject) {
		return 200, jsonObject{
			"host": mackerel.Host{
				ID:     "xxx12345678901",
				Name:   "host.example.com",
				Type:   "unknown",
				Status: "standby",
			},
		}
	}

	c, _ := Prepare(&conf, &AgentMeta{})
	api := c.API
	host := c.Host

	if api.BaseURL.String() != ts.URL {
		t.Errorf("Apibase mismatch: %s != %s", api.BaseURL, ts.URL)
	}

	if host.ID != "xxx12345678901" {
		t.Error("Host ID mismatch", host)
	}

	if host.Name != "host.example.com" {
		t.Error("Host name mismatch", host)
	}
}

func TestCollectHostSpecs(t *testing.T) {
	conf := config.Config{}
	hostname, meta, _ /*interfaces*/, _ /*customIdentifier*/, err := collectHostSpecs(&conf)

	if err != nil {
		t.Errorf("collectHostSpecs should not fail: %s", err)
	}

	if hostname == "" {
		t.Error("hostname should not be empty")
	}

	if _, ok := meta["cpu"]; !ok {
		t.Error("meta.cpu should exist")
	}
}

type counterGenerator struct {
	counter int
	sync.Mutex
}

func (g *counterGenerator) Generate() (metrics.Values, error) {
	g.Lock()
	defer g.Unlock()
	g.counter = g.counter + 1
	return map[string]float64{"dummy.a": float64(g.counter)}, nil
}

type byTime []mackerel.CreatingMetricsValue

func (b byTime) Len() int           { return len(b) }
func (b byTime) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }
func (b byTime) Less(i, j int) bool { return b[i].Time < b[j].Time }

func TestLoop(t *testing.T) {
	if testing.Verbose() {
		logging.SetLogLevel(logging.DEBUG)
	}

	conf, mockHandlers, ts := newMockAPIServer(t)
	defer ts.Close()

	if testing.Short() {
		// Shrink time scale
		originalPostMetricsInterval := config.PostMetricsInterval

		config.PostMetricsInterval = 10 * time.Second
		ratio := config.PostMetricsInterval.Seconds() / originalPostMetricsInterval.Seconds()

		conf.Connection.PostMetricsDequeueDelaySeconds =
			int(float64(config.DefaultConfig.Connection.PostMetricsRetryDelaySeconds) * ratio)
		conf.Connection.PostMetricsRetryDelaySeconds =
			int(float64(config.DefaultConfig.Connection.PostMetricsRetryDelaySeconds) * ratio)

		defer func() {
			config.PostMetricsInterval = originalPostMetricsInterval
		}()
	}

	/// Simulate the situation that mackerel.io is down for 3 min
	// Strategy:
	// counterGenerator generates values 1,2,3,4,...
	// when we got value 3, the server will start responding 503 for three times (inclusive)
	// so the agent should queue the generated values and retry sending.
	//
	//  status: o . o . x . x . x . o o o o o
	//    send: 1 . 2 . 3 . 3 . 3 . 3 4 5 6 7
	// collect: 1 . 2 . 3 . 4 . 5 . 6 . 7 . 8
	//           ^
	//           30s
	const (
		totalFailures = 3
		totalPosts    = 7
	)
	failureCount := 0
	receivedDataPoints := []mackerel.CreatingMetricsValue{}
	done := make(chan struct{})

	mockHandlers["POST /api/v0/tsdb"] = func(req *http.Request) (int, jsonObject) {
		payload := []mackerel.CreatingMetricsValue{}
		json.NewDecoder(req.Body).Decode(&payload)

		for _, p := range payload {
			value := p.Value.(float64)
			if value == 3 {
				failureCount++
				if failureCount <= totalFailures {
					return 503, jsonObject{
						"failure": failureCount, // just for DEBUG logging
					}
				}
			}

			if value == totalPosts {
				defer func() { done <- struct{}{} }()
			}
		}

		receivedDataPoints = append(receivedDataPoints, payload...)

		return 200, jsonObject{
			"success": true,
		}
	}
	mockHandlers["PUT /api/v0/hosts/xyzabc12345"] = func(req *http.Request) (int, jsonObject) {
		return 200, jsonObject{
			"result": "OK",
		}
	}

	// Prepare required objects...
	ag := &agent.Agent{
		MetricsGenerators: []metrics.Generator{
			&counterGenerator{},
		},
	}

	api, err := mackerel.NewAPI(conf.Apibase, conf.Apikey, true)
	if err != nil {
		t.Fatal(err)
	}

	host := &mackerel.Host{ID: "xyzabc12345"}

	termCh := make(chan struct{})
	exitCh := make(chan error)
	app := &App{
		Agent:     ag,
		Config:    &conf,
		API:       api,
		Host:      host,
		AgentMeta: &AgentMeta{},
	}
	// Start looping!
	go func() {
		exitCh <- loop(app, termCh)
	}()

	<-done

	// Verify results
	if len(receivedDataPoints) != totalPosts {
		t.Errorf("the agent should have sent %d datapoints, got: %+v", totalPosts, receivedDataPoints)
	}

	sort.Sort(byTime(receivedDataPoints))

	for i := 0; i < totalPosts; i++ {
		value := receivedDataPoints[i].Value.(float64)
		if value != float64(i+1) {
			t.Errorf("the %dth datapoint should have value %d, got: %+v", i, i+1, receivedDataPoints)
		}
	}

	termCh <- struct{}{}
	exitErr := <-exitCh
	if exitErr != nil {
		t.Errorf("exitErr should be nil, got: %s", exitErr)
	}
}

func TestReportCheckMonitors(t *testing.T) {
	if testing.Verbose() {
		logging.SetLogLevel(logging.DEBUG)
	}

	cases := []struct {
		Status      int
		expectRetry bool
	}{
		{http.StatusOK, false},
		{http.StatusBadRequest, false},
		{http.StatusInternalServerError, true},
	}

	for _, tc := range cases {
		conf, mockHandlers, ts := newMockAPIServer(t)
		defer ts.Close()

		if testing.Short() {
			conf.Connection.ReportCheckRetryDelaySeconds = 1
		}

		postCount := 0
		retried := false
		mu := &sync.Mutex{}

		mockHandlers["POST /api/v0/monitoring/checks/report"] = func(req *http.Request) (int, jsonObject) {
			mu.Lock()
			defer mu.Unlock()
			postCount++
			if postCount > 1 {
				retried = true
			}
			return tc.Status, jsonObject{}
		}

		api, err := mackerel.NewAPI(conf.Apibase, conf.Apikey, true)
		if err != nil {
			t.Fatal(err)
		}

		host := &mackerel.Host{ID: "xyzabc12345"}

		app := &App{
			Agent:     &agent.Agent{},
			Config:    &conf,
			API:       api,
			Host:      host,
			AgentMeta: &AgentMeta{},
		}

		go func() {
			reportCheckMonitors(app, []*checks.Report{})
		}()

		time.Sleep(time.Duration(conf.Connection.ReportCheckRetryDelaySeconds) * 3 * time.Second)

		mu.Lock()
		defer mu.Unlock()

		if retried != tc.expectRetry {
			text := http.StatusText(tc.Status)
			if tc.expectRetry {
				t.Errorf("the agent should have resend reports when got %d %q", tc.Status, text)
			} else {
				t.Errorf("the agent should not have resend reports when got %d %q", tc.Status, text)
			}
		}
	}
}
<|endoftext|>


<commit_before>package jira

import (
	"fmt"
	"net/http"
)

type ProjectService struct {
	client *Client
}

// Project list type
type ProjectList []struct {
	Expand     string `json:"expand"`
	Self       string `json:"self"`
	ID         string `json:"id"`
	Key        string `json:"key"`
	Name       string `json:"name"`
	AvatarUrls struct {
		Four8X48  string `json:"48x48"`
		Two4X24   string `json:"24x24"`
		One6X16   string `json:"16x16"`
		Three2X32 string `json:"32x32"`
	} `json:"avatarUrls"`
	ProjectTypeKey  string `json:"projectTypeKey"`
	ProjectCategory struct {
		Self        string `json:"self"`
		ID          string `json:"id"`
		Name        string `json:"name"`
		Description string `json:"description"`
	} `json:"projectCategory,omitempty"`
}

// Full project description
// Can't name it project because it exist in issue
type FullProject struct {
	Expand      string `json:"expand"`
	Self        string `json:"self"`
	ID          string `json:"id"`
	Key         string `json:"key"`
	Description string `json:"description"`
	Lead        struct {
		Self       string `json:"self"`
		Name       string `json:"name"`
		AvatarUrls struct {
			Four8X48  string `json:"48x48"`
			Two4X24   string `json:"24x24"`
			One6X16   string `json:"16x16"`
			Three2X32 string `json:"32x32"`
		} `json:"avatarUrls"`
		DisplayName string `json:"displayName"`
		Active      bool   `json:"active"`
	} `json:"lead"`
	Components []struct {
		Self        string `json:"self"`
		ID          string `json:"id"`
		Name        string `json:"name"`
		Description string `json:"description"`
		Lead        struct {
			Self       string `json:"self"`
			Name       string `json:"name"`
			AvatarUrls struct {
				Four8X48  string `json:"48x48"`
				Two4X24   string `json:"24x24"`
				One6X16   string `json:"16x16"`
				Three2X32 string `json:"32x32"`
			} `json:"avatarUrls"`
			DisplayName string `json:"displayName"`
			Active      bool   `json:"active"`
		} `json:"lead"`
		AssigneeType string `json:"assigneeType"`
		Assignee     struct {
			Self       string `json:"self"`
			Name       string `json:"name"`
			AvatarUrls struct {
				Four8X48  string `json:"48x48"`
				Two4X24   string `json:"24x24"`
				One6X16   string `json:"16x16"`
				Three2X32 string `json:"32x32"`
			} `json:"avatarUrls"`
			DisplayName string `json:"displayName"`
			Active      bool   `json:"active"`
		} `json:"assignee"`
		RealAssigneeType string `json:"realAssigneeType"`
		RealAssignee     struct {
			Self       string `json:"self"`
			Name       string `json:"name"`
			AvatarUrls struct {
				Four8X48  string `json:"48x48"`
				Two4X24   string `json:"24x24"`
				One6X16   string `json:"16x16"`
				Three2X32 string `json:"32x32"`
			} `json:"avatarUrls"`
			DisplayName string `json:"displayName"`
			Active      bool   `json:"active"`
		} `json:"realAssignee"`
		IsAssigneeTypeValid bool   `json:"isAssigneeTypeValid"`
		Project             string `json:"project"`
		ProjectID           int    `json:"projectId"`
	} `json:"components"`
	IssueTypes []struct {
		Self        string `json:"self"`
		ID          string `json:"id"`
		Description string `json:"description"`
		IconURL     string `json:"iconUrl"`
		Name        string `json:"name"`
		Subtask     bool   `json:"subtask"`
		AvatarID    int    `json:"avatarId"`
	} `json:"issueTypes"`
	URL          string        `json:"url"`
	Email        string        `json:"email"`
	AssigneeType string        `json:"assigneeType"`
	Versions     []interface{} `json:"versions"`
	Name         string        `json:"name"`
	Roles        struct {
		Developers string `json:"Developers"`
	} `json:"roles"`
	AvatarUrls struct {
		Four8X48  string `json:"48x48"`
		Two4X24   string `json:"24x24"`
		One6X16   string `json:"16x16"`
		Three2X32 string `json:"32x32"`
	} `json:"avatarUrls"`
	ProjectCategory struct {
		Self        string `json:"self"`
		ID          string `json:"id"`
		Name        string `json:"name"`
		Description string `json:"description"`
	} `json:"projectCategory"`
}

// Get all projects form jira
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects
func (s *ProjectService) GetList() (*ProjectList, *http.Response, error) {

	apiEndpoint := "rest/api/2/project"
	req, err := s.client.NewRequest("GET", apiEndpoint, nil)

	if err != nil {
		return nil, nil, err
	}

	projectList := new(ProjectList)
	resp, err := s.client.Do(req, projectList)
	if err != nil {
		return nil, resp, err
	}
	return projectList, resp, nil
}

// Get returns a full representation of the project for the given issue key.
// JIRA will attempt to identify the project by the projectIdOrKey path parameter.
// This can be an project id, or an project key.
//
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject
func (s *ProjectService) Get(projectID string) (*FullProject, *http.Response, error) {
	apiEndpoint := fmt.Sprintf("/rest/api/2/project/%s", projectID)
	req, err := s.client.NewRequest("GET", apiEndpoint, nil)

	if err != nil {
		return nil, nil, err
	}

	project := new(FullProject)
	resp, err := s.client.Do(req, project)
	if err != nil {
		return nil, resp, err
	}
	return project, resp, nil
}
<commit_msg>remove old project<commit_after><|endoftext|>