<commit_before>package runtimes

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/aisk/chrysanthemum"
	"github.com/facebookgo/parseignore"
	"github.com/facebookgo/symwalk"
	"github.com/fsnotify/fsnotify"
	"github.com/leancloud/lean-cli/utils"
)

// ErrInvalidRuntime means the project's structure is not a valid LeanEngine project
var ErrInvalidRuntime = errors.New("invalid runtime")

type filesPattern struct {
	Includes []string
	Excludes []string
}

// Runtime stands for a language runtime
type Runtime struct {
	command     *exec.Cmd
	ProjectPath string
	Name        string
	Exec        string
	Args        []string
	WatchFiles  []string
	Envs        []string
	Port        string
	// DeployFiles is the patterns for source code to deploy to the remote server
	DeployFiles filesPattern
	// Errors is the channel that receives the command's error result
	Errors chan error
}

// Run the project, and watch file changes
func (runtime *Runtime) Run() {
	go func() {
		for {
			runtime.command = exec.Command(runtime.Exec, runtime.Args...)
			runtime.command.Env = os.Environ()
			runtime.command.Stdout = os.Stdout
			runtime.command.Stderr = os.Stderr
			runtime.command.Env = os.Environ()

			for _, env := range runtime.Envs {
				runtime.command.Env = append(runtime.command.Env, env)
			}

			chrysanthemum.Printf("项目已启动，请使用浏览器访问：http://localhost:%s\r\n", runtime.Port)
			err := runtime.command.Run()
			// TODO: this maybe not portable
			if err.Error() == "signal: killed" {
				continue
			} else {
				runtime.Errors <- err
				break
			}
		}
	}()
}

// Watch file changes
func (runtime *Runtime) Watch(interval time.Duration) error {

	// watch file changes
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return err
	}

	lastFiredTime := time.Now()

	go func() {
		for {
			select {
			case event := <-watcher.Events:
				_ = event
				now := time.Now()
				if now.Sub(lastFiredTime) > interval {
					err = runtime.command.Process.Kill()
					if err != nil {
						runtime.Errors <- err
					}
					lastFiredTime = now
				}
			case err = <-watcher.Errors:
				runtime.Errors <- err
			}
		}
	}()
	for _, file := range runtime.WatchFiles {
		err = watcher.Add(file)
		if err != nil {
			return err
		}
	}

	return nil
}

func (runtime *Runtime) ArchiveUploadFiles(archiveFile string, ignoreFilePath string) error {
	return runtime.defaultArchive(archiveFile, ignoreFilePath)
}

func (runtime *Runtime) defaultArchive(archiveFile string, ignoreFilePath string) error {
	matcher, err := runtime.readIgnore(ignoreFilePath)
	if os.IsNotExist(err) {
		return fmt.Errorf("指定的 ignore 文件 '%s' 不存在", ignoreFilePath)
	} else if err != nil {
		return err
	}

	files := []struct{ Name, Path string }{}
	err = symwalk.Walk(".", func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		// convert DOS's '\' path seprater to UNIX style
		path = filepath.ToSlash(path)
		decision, err := matcher.Match(path, info)
		if err != nil {
			return err
		}

		if info.IsDir() {
			if decision == parseignore.Exclude {
				return filepath.SkipDir
			}
			return nil
		}

		if decision != parseignore.Exclude {
			files = append(files, struct{ Name, Path string }{
				Name: path,
				Path: path,
			})
		}
		return nil
	})

	if err != nil {
		return err
	}
	spinner := chrysanthemum.New("压缩项目文件").Start()

	err = utils.ArchiveFiles(archiveFile, files)
	if err != nil {
		spinner.Failed()
	}
	spinner.Successed()
	return nil
}

// DetectRuntime returns the project's runtime
func DetectRuntime(projectPath string) (*Runtime, error) {
	bar := chrysanthemum.New("正在检测运行时").Start()
	// order is important
	if utils.IsFileExists(filepath.Join(projectPath, "cloud", "main.js")) {
		chrysanthemum.Printf("检测到 cloudcode 运行时\r\n")
		bar.Successed()
		return &Runtime{
			Name: "cloudcode",
		}, nil
	}
	if utils.IsFileExists(filepath.Join(projectPath, "server.js")) && utils.IsFileExists(filepath.Join(projectPath, "package.json")) {
		bar.Successed()
		chrysanthemum.Printf("检测到 node.js 运行时\r\n")
		return newNodeRuntime(projectPath)
	}
	if utils.IsFileExists(filepath.Join(projectPath, "package.json")) {
		data, err := ioutil.ReadFile(filepath.Join(projectPath, "package.json"))
		if err == nil {
			var result struct {
				Scripts struct {
					Start string `json:"start"`
				} `json:"scripts"`
			}
			if err = json.Unmarshal(data, &result); err == nil {
				if result.Scripts.Start != "" {
					bar.Successed()
					chrysanthemum.Printf("检测到 node.js 运行时\r\n")
					return newNodeRuntime(projectPath)
				}
			}
		}
	}
	if utils.IsFileExists(filepath.Join(projectPath, "requirements.txt")) && utils.IsFileExists(filepath.Join(projectPath, "wsgi.py")) {
		bar.Successed()
		chrysanthemum.Printf("检测到 Python 运行时\r\n")
		return newPythonRuntime(projectPath)
	}
	if utils.IsFileExists(filepath.Join(projectPath, "composer.json")) && utils.IsFileExists(filepath.Join("public", "index.php")) {
		bar.Successed()
		chrysanthemum.Printf("检测到 PHP 运行时\r\n")
		return newPhpRuntime(projectPath)
	}
	if utils.IsFileExists(filepath.Join(projectPath, "pom.xml")) {
		bar.Successed()
		chrysanthemum.Printf("检测到 Java 运行时\r\n")
		return newJavaRuntime(projectPath)
	}
	bar.Failed()
	return nil, ErrInvalidRuntime
}

func lookupBin(fallbacks []string) (string, error) {
	for i, bin := range fallbacks {
		binPath, err := exec.LookPath(bin)
		if err == nil { // found
			if i == 0 {
				chrysanthemum.Printf("找到运行文件 `%s`\r\n", binPath)
			} else {
				chrysanthemum.Printf("没有找到命令 `%s`，使用 `%s` 代替 \r\n", fallbacks[i-1], fallbacks[i])
			}
			return bin, nil
		}
	}

	return "", fmt.Errorf("`%s` not found", fallbacks[0])
}

func newPythonRuntime(projectPath string) (*Runtime, error) {
	execName := "python2.7"

	content, err := ioutil.ReadFile(filepath.Join(projectPath, "runtime.txt"))
	if err != nil {
		if !os.IsNotExist(err) {
			return nil, err
		}
		// the default content
		content = []byte("python-2.7")
	}
	if strings.HasPrefix(string(content), "python-2.7") {
		execName, err = lookupBin([]string{"python2.7", "python2", "python"})
		if err != nil {
			return nil, err
		}
	} else if strings.HasPrefix(string(content), "python-3.5") {
		execName, err = lookupBin([]string{"python3.5", "python3", "python"})
		if err != nil {
			return nil, err
		}
	} else {
		return nil, errors.New("invalid python runtime.txt format, only `python-2.7` and `python-3.5` were allowed")
	}

	return &Runtime{
		ProjectPath: projectPath,
		Name:        "python",
		Exec:        execName,
		Args:        []string{"wsgi.py"},
		WatchFiles:  []string{"."},
		Envs:        os.Environ(),
		Errors:      make(chan error),
	}, nil
}

func newNodeRuntime(projectPath string) (*Runtime, error) {
	execName := "node"
	args := []string{"server.js"}
	pkgFile := filepath.Join(projectPath, "package.json")
	if content, err := ioutil.ReadFile(pkgFile); err == nil {
		pkg := new(struct {
			Scripts struct {
				Start string `json:"start"`
				Dev   string `json:"dev"`
			} `json:"scripts"`
		})
		err = json.Unmarshal(content, pkg)
		if err != nil {
			return nil, err
		}

		if pkg.Scripts.Dev != "" {
			execName = "npm"
			args = []string{"run", "dev"}
		} else if pkg.Scripts.Start != "" {
			execName = "npm"
			args = []string{"start"}
		}
	}

	return &Runtime{
		ProjectPath: projectPath,
		Name:        "node.js",
		Exec:        execName,
		Args:        args,
		WatchFiles:  []string{"."},
		Envs:        os.Environ(),
		Errors:      make(chan error),
	}, nil
}

func newJavaRuntime(projectPath string) (*Runtime, error) {
	return &Runtime{
		ProjectPath: projectPath,
		Name:        "java",
		Exec:        "mvn",
		Args:        []string{"jetty:run"},
		WatchFiles:  []string{"."},
		Envs:        os.Environ(),
		Errors:      make(chan error),
	}, nil
}

func newPhpRuntime(projectPath string) (*Runtime, error) {
	entryScript, err := getPHPEntryScriptPath()
	if err != nil {
		return nil, err
	}
	return &Runtime{
		ProjectPath: projectPath,
		Name:        "php",
		Exec:        "php",
		Args:        []string{"-S", "127.0.0.1:3000", "-t", "public", entryScript},
		WatchFiles:  []string{"."},
		Envs:        os.Environ(),
		Errors:      make(chan error),
	}, nil
}
<commit_msg>:boom: show a warning on lean up while using leanengine sdk version less than 1.0.0<commit_after>package runtimes

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/aisk/chrysanthemum"
	"github.com/facebookgo/parseignore"
	"github.com/facebookgo/symwalk"
	"github.com/fsnotify/fsnotify"
	"github.com/leancloud/lean-cli/utils"
)

// ErrInvalidRuntime means the project's structure is not a valid LeanEngine project
var ErrInvalidRuntime = errors.New("invalid runtime")

type filesPattern struct {
	Includes []string
	Excludes []string
}

// Runtime stands for a language runtime
type Runtime struct {
	command     *exec.Cmd
	ProjectPath string
	Name        string
	Exec        string
	Args        []string
	WatchFiles  []string
	Envs        []string
	Port        string
	// DeployFiles is the patterns for source code to deploy to the remote server
	DeployFiles filesPattern
	// Errors is the channel that receives the command's error result
	Errors chan error
}

// Run the project, and watch file changes
func (runtime *Runtime) Run() {
	go func() {
		for {
			runtime.command = exec.Command(runtime.Exec, runtime.Args...)
			runtime.command.Env = os.Environ()
			runtime.command.Stdout = os.Stdout
			runtime.command.Stderr = os.Stderr
			runtime.command.Env = os.Environ()

			for _, env := range runtime.Envs {
				runtime.command.Env = append(runtime.command.Env, env)
			}

			chrysanthemum.Printf("项目已启动，请使用浏览器访问：http://localhost:%s\r\n", runtime.Port)
			err := runtime.command.Run()
			// TODO: this maybe not portable
			if err.Error() == "signal: killed" {
				continue
			} else {
				runtime.Errors <- err
				break
			}
		}
	}()
}

// Watch file changes
func (runtime *Runtime) Watch(interval time.Duration) error {

	// watch file changes
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return err
	}

	lastFiredTime := time.Now()

	go func() {
		for {
			select {
			case event := <-watcher.Events:
				_ = event
				now := time.Now()
				if now.Sub(lastFiredTime) > interval {
					err = runtime.command.Process.Kill()
					if err != nil {
						runtime.Errors <- err
					}
					lastFiredTime = now
				}
			case err = <-watcher.Errors:
				runtime.Errors <- err
			}
		}
	}()
	for _, file := range runtime.WatchFiles {
		err = watcher.Add(file)
		if err != nil {
			return err
		}
	}

	return nil
}

func (runtime *Runtime) ArchiveUploadFiles(archiveFile string, ignoreFilePath string) error {
	return runtime.defaultArchive(archiveFile, ignoreFilePath)
}

func (runtime *Runtime) defaultArchive(archiveFile string, ignoreFilePath string) error {
	matcher, err := runtime.readIgnore(ignoreFilePath)
	if os.IsNotExist(err) {
		return fmt.Errorf("指定的 ignore 文件 '%s' 不存在", ignoreFilePath)
	} else if err != nil {
		return err
	}

	files := []struct{ Name, Path string }{}
	err = symwalk.Walk(".", func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		// convert DOS's '\' path seprater to UNIX style
		path = filepath.ToSlash(path)
		decision, err := matcher.Match(path, info)
		if err != nil {
			return err
		}

		if info.IsDir() {
			if decision == parseignore.Exclude {
				return filepath.SkipDir
			}
			return nil
		}

		if decision != parseignore.Exclude {
			files = append(files, struct{ Name, Path string }{
				Name: path,
				Path: path,
			})
		}
		return nil
	})

	if err != nil {
		return err
	}
	spinner := chrysanthemum.New("压缩项目文件").Start()

	err = utils.ArchiveFiles(archiveFile, files)
	if err != nil {
		spinner.Failed()
	}
	spinner.Successed()
	return nil
}

// DetectRuntime returns the project's runtime
func DetectRuntime(projectPath string) (*Runtime, error) {
	bar := chrysanthemum.New("正在检测运行时").Start()
	// order is important
	if utils.IsFileExists(filepath.Join(projectPath, "cloud", "main.js")) {
		chrysanthemum.Printf("检测到 cloudcode 运行时\r\n")
		bar.Successed()
		return &Runtime{
			Name: "cloudcode",
		}, nil
	}
	if utils.IsFileExists(filepath.Join(projectPath, "server.js")) && utils.IsFileExists(filepath.Join(projectPath, "package.json")) {
		bar.Successed()
		chrysanthemum.Printf("检测到 node.js 运行时\r\n")
		return newNodeRuntime(projectPath)
	}
	if utils.IsFileExists(filepath.Join(projectPath, "package.json")) {
		data, err := ioutil.ReadFile(filepath.Join(projectPath, "package.json"))
		if err == nil {
			var result struct {
				Scripts struct {
					Start string `json:"start"`
				} `json:"scripts"`
			}
			if err = json.Unmarshal(data, &result); err == nil {
				if result.Scripts.Start != "" {
					bar.Successed()
					chrysanthemum.Printf("检测到 node.js 运行时\r\n")
					return newNodeRuntime(projectPath)
				}
			}
		}
	}
	if utils.IsFileExists(filepath.Join(projectPath, "requirements.txt")) && utils.IsFileExists(filepath.Join(projectPath, "wsgi.py")) {
		bar.Successed()
		chrysanthemum.Printf("检测到 Python 运行时\r\n")
		return newPythonRuntime(projectPath)
	}
	if utils.IsFileExists(filepath.Join(projectPath, "composer.json")) && utils.IsFileExists(filepath.Join("public", "index.php")) {
		bar.Successed()
		chrysanthemum.Printf("检测到 PHP 运行时\r\n")
		return newPhpRuntime(projectPath)
	}
	if utils.IsFileExists(filepath.Join(projectPath, "pom.xml")) {
		bar.Successed()
		chrysanthemum.Printf("检测到 Java 运行时\r\n")
		return newJavaRuntime(projectPath)
	}
	bar.Failed()
	return nil, ErrInvalidRuntime
}

func lookupBin(fallbacks []string) (string, error) {
	for i, bin := range fallbacks {
		binPath, err := exec.LookPath(bin)
		if err == nil { // found
			if i == 0 {
				chrysanthemum.Printf("找到运行文件 `%s`\r\n", binPath)
			} else {
				chrysanthemum.Printf("没有找到命令 `%s`，使用 `%s` 代替 \r\n", fallbacks[i-1], fallbacks[i])
			}
			return bin, nil
		}
	}

	return "", fmt.Errorf("`%s` not found", fallbacks[0])
}

func newPythonRuntime(projectPath string) (*Runtime, error) {
	execName := "python2.7"

	content, err := ioutil.ReadFile(filepath.Join(projectPath, "runtime.txt"))
	if err != nil {
		if !os.IsNotExist(err) {
			return nil, err
		}
		// the default content
		content = []byte("python-2.7")
	}
	if strings.HasPrefix(string(content), "python-2.7") {
		execName, err = lookupBin([]string{"python2.7", "python2", "python"})
		if err != nil {
			return nil, err
		}
	} else if strings.HasPrefix(string(content), "python-3.5") {
		execName, err = lookupBin([]string{"python3.5", "python3", "python"})
		if err != nil {
			return nil, err
		}
	} else {
		return nil, errors.New("invalid python runtime.txt format, only `python-2.7` and `python-3.5` were allowed")
	}

	return &Runtime{
		ProjectPath: projectPath,
		Name:        "python",
		Exec:        execName,
		Args:        []string{"wsgi.py"},
		WatchFiles:  []string{"."},
		Envs:        os.Environ(),
		Errors:      make(chan error),
	}, nil
}

func newNodeRuntime(projectPath string) (*Runtime, error) {
	execName := "node"
	args := []string{"server.js"}
	pkgFile := filepath.Join(projectPath, "package.json")
	if content, err := ioutil.ReadFile(pkgFile); err == nil {
		pkg := new(struct {
			Scripts struct {
				Start string `json:"start"`
				Dev   string `json:"dev"`
			} `json:"scripts"`
			Dependencies map[string]string `json:"dependencies"`
		})
		err = json.Unmarshal(content, pkg)
		if err != nil {
			return nil, err
		}

		if pkg.Scripts.Dev != "" {
			execName = "npm"
			args = []string{"run", "dev"}
		} else if pkg.Scripts.Start != "" {
			execName = "npm"
			args = []string{"start"}
		}

		if sdkVersion, ok := pkg.Dependencies["leanengine"]; ok {
			if strings.HasPrefix(sdkVersion, "0.") ||
				strings.HasPrefix(sdkVersion, "~0.") ||
				strings.HasPrefix(sdkVersion, "^0.") {
				s := "当前使用 leanengine SDK 版本过低，本地云函数调试功能将会不能正常启用。建议参考 http://url.leanapp.cn/Og1cVia 尽快升级。"
				fmt.Fprintf(os.Stderr, " %s [WARNING] %s\r\n", chrysanthemum.Fail, s)
			}
		}

	}

	return &Runtime{
		ProjectPath: projectPath,
		Name:        "node.js",
		Exec:        execName,
		Args:        args,
		WatchFiles:  []string{"."},
		Envs:        os.Environ(),
		Errors:      make(chan error),
	}, nil
}

func newJavaRuntime(projectPath string) (*Runtime, error) {
	return &Runtime{
		ProjectPath: projectPath,
		Name:        "java",
		Exec:        "mvn",
		Args:        []string{"jetty:run"},
		WatchFiles:  []string{"."},
		Envs:        os.Environ(),
		Errors:      make(chan error),
	}, nil
}

func newPhpRuntime(projectPath string) (*Runtime, error) {
	entryScript, err := getPHPEntryScriptPath()
	if err != nil {
		return nil, err
	}
	return &Runtime{
		ProjectPath: projectPath,
		Name:        "php",
		Exec:        "php",
		Args:        []string{"-S", "127.0.0.1:3000", "-t", "public", entryScript},
		WatchFiles:  []string{"."},
		Envs:        os.Environ(),
		Errors:      make(chan error),
	}, nil
}
<|endoftext|>


<commit_before>// Copyright 2013 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 runtime

import (
	"unsafe"
)

const _DWORD_MAX = 0xffffffff

//go:cgo_import_dynamic runtime._CreateIoCompletionPort CreateIoCompletionPort "kernel32.dll"
//go:cgo_import_dynamic runtime._GetQueuedCompletionStatus GetQueuedCompletionStatus "kernel32.dll"
//go:cgo_import_dynamic runtime._WSAGetOverlappedResult WSAGetOverlappedResult "ws2_32.dll"

var (
	_CreateIoCompletionPort,
	_GetQueuedCompletionStatus,
	_WSAGetOverlappedResult stdFunction
)

const _INVALID_HANDLE_VALUE = ^uintptr(0)

// net_op must be the same as beginning of net.operation. Keep these in sync.
type net_op struct {
	// used by windows
	o overlapped
	// used by netpoll
	pd    *pollDesc
	mode  int32
	errno int32
	qty   uint32
}

type overlappedEntry struct {
	key      uintptr
	op       *net_op // In reality it's *overlapped, but we cast it to *net_op anyway.
	internal uintptr
	qty      uint32
}

var iocphandle uintptr = _INVALID_HANDLE_VALUE // completion port io handle

func netpollinit() {
	iocphandle = uintptr(stdcall4(_CreateIoCompletionPort, _INVALID_HANDLE_VALUE, 0, 0, _DWORD_MAX))
	if iocphandle == 0 {
		println("netpoll: failed to create iocp handle (errno=", getlasterror(), ")")
		gothrow("netpoll: failed to create iocp handle")
	}
}

func netpollopen(fd uintptr, pd *pollDesc) int32 {
	if stdcall4(_CreateIoCompletionPort, fd, iocphandle, 0, 0) == 0 {
		return -int32(getlasterror())
	}
	return 0
}

func netpollclose(fd uintptr) int32 {
	// nothing to do
	return 0
}

func netpollarm(pd *pollDesc, mode int) {
	gothrow("unused")
}

// Polls for completed network IO.
// Returns list of goroutines that become runnable.
func netpoll(block bool) *g {
	var entries [64]overlappedEntry
	var wait, qty, key, flags, n, i uint32
	var errno int32
	var op *net_op
	var gp *g

	mp := getg().m

	if iocphandle == _INVALID_HANDLE_VALUE {
		return nil
	}
	gp = nil
	wait = 0
	if block {
		wait = _INFINITE
	}
retry:
	if _GetQueuedCompletionStatusEx != nil {
		n = uint32(len(entries) / int(gomaxprocs))
		if n < 8 {
			n = 8
		}
		if block {
			mp.blocked = true
		}
		if stdcall6(_GetQueuedCompletionStatusEx, iocphandle, uintptr(unsafe.Pointer(&entries[0])), uintptr(n), uintptr(unsafe.Pointer(&n)), uintptr(wait), 0) == 0 {
			mp.blocked = false
			errno = int32(getlasterror())
			if !block && errno == _WAIT_TIMEOUT {
				return nil
			}
			println("netpoll: GetQueuedCompletionStatusEx failed (errno=", errno, ")")
			gothrow("netpoll: GetQueuedCompletionStatusEx failed")
		}
		mp.blocked = false
		for i = 0; i < n; i++ {
			op = entries[i].op
			errno = 0
			qty = 0
			if stdcall5(_WSAGetOverlappedResult, netpollfd(op.pd), uintptr(unsafe.Pointer(op)), uintptr(unsafe.Pointer(&qty)), 0, uintptr(unsafe.Pointer(&flags))) == 0 {
				errno = int32(getlasterror())
			}
			handlecompletion(&gp, op, errno, qty)
		}
	} else {
		op = nil
		errno = 0
		qty = 0
		if block {
			mp.blocked = true
		}
		if stdcall5(_GetQueuedCompletionStatus, iocphandle, uintptr(unsafe.Pointer(&qty)), uintptr(unsafe.Pointer(&key)), uintptr(unsafe.Pointer(&op)), uintptr(wait)) == 0 {
			mp.blocked = false
			errno = int32(getlasterror())
			if !block && errno == _WAIT_TIMEOUT {
				return nil
			}
			if op == nil {
				println("netpoll: GetQueuedCompletionStatus failed (errno=", errno, ")")
				gothrow("netpoll: GetQueuedCompletionStatus failed")
			}
			// dequeued failed IO packet, so report that
		}
		mp.blocked = false
		handlecompletion(&gp, op, errno, qty)
	}
	if block && gp == nil {
		goto retry
	}
	return gp
}

func handlecompletion(gpp **g, op *net_op, errno int32, qty uint32) {
	if op == nil {
		gothrow("netpoll: GetQueuedCompletionStatus returned op == nil")
	}
	mode := op.mode
	if mode != 'r' && mode != 'w' {
		println("netpoll: GetQueuedCompletionStatus returned invalid mode=", mode)
		gothrow("netpoll: GetQueuedCompletionStatus returned invalid mode")
	}
	op.errno = errno
	op.qty = qty
	netpollready(gpp, op.pd, mode)
}
<commit_msg>[dev.cc] runtime: replace deleted netpollfd function<commit_after>// Copyright 2013 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 runtime

import (
	"unsafe"
)

const _DWORD_MAX = 0xffffffff

//go:cgo_import_dynamic runtime._CreateIoCompletionPort CreateIoCompletionPort "kernel32.dll"
//go:cgo_import_dynamic runtime._GetQueuedCompletionStatus GetQueuedCompletionStatus "kernel32.dll"
//go:cgo_import_dynamic runtime._WSAGetOverlappedResult WSAGetOverlappedResult "ws2_32.dll"

var (
	_CreateIoCompletionPort,
	_GetQueuedCompletionStatus,
	_WSAGetOverlappedResult stdFunction
)

const _INVALID_HANDLE_VALUE = ^uintptr(0)

// net_op must be the same as beginning of net.operation. Keep these in sync.
type net_op struct {
	// used by windows
	o overlapped
	// used by netpoll
	pd    *pollDesc
	mode  int32
	errno int32
	qty   uint32
}

type overlappedEntry struct {
	key      uintptr
	op       *net_op // In reality it's *overlapped, but we cast it to *net_op anyway.
	internal uintptr
	qty      uint32
}

var iocphandle uintptr = _INVALID_HANDLE_VALUE // completion port io handle

func netpollinit() {
	iocphandle = uintptr(stdcall4(_CreateIoCompletionPort, _INVALID_HANDLE_VALUE, 0, 0, _DWORD_MAX))
	if iocphandle == 0 {
		println("netpoll: failed to create iocp handle (errno=", getlasterror(), ")")
		gothrow("netpoll: failed to create iocp handle")
	}
}

func netpollopen(fd uintptr, pd *pollDesc) int32 {
	if stdcall4(_CreateIoCompletionPort, fd, iocphandle, 0, 0) == 0 {
		return -int32(getlasterror())
	}
	return 0
}

func netpollclose(fd uintptr) int32 {
	// nothing to do
	return 0
}

func netpollarm(pd *pollDesc, mode int) {
	gothrow("unused")
}

// Polls for completed network IO.
// Returns list of goroutines that become runnable.
func netpoll(block bool) *g {
	var entries [64]overlappedEntry
	var wait, qty, key, flags, n, i uint32
	var errno int32
	var op *net_op
	var gp *g

	mp := getg().m

	if iocphandle == _INVALID_HANDLE_VALUE {
		return nil
	}
	gp = nil
	wait = 0
	if block {
		wait = _INFINITE
	}
retry:
	if _GetQueuedCompletionStatusEx != nil {
		n = uint32(len(entries) / int(gomaxprocs))
		if n < 8 {
			n = 8
		}
		if block {
			mp.blocked = true
		}
		if stdcall6(_GetQueuedCompletionStatusEx, iocphandle, uintptr(unsafe.Pointer(&entries[0])), uintptr(n), uintptr(unsafe.Pointer(&n)), uintptr(wait), 0) == 0 {
			mp.blocked = false
			errno = int32(getlasterror())
			if !block && errno == _WAIT_TIMEOUT {
				return nil
			}
			println("netpoll: GetQueuedCompletionStatusEx failed (errno=", errno, ")")
			gothrow("netpoll: GetQueuedCompletionStatusEx failed")
		}
		mp.blocked = false
		for i = 0; i < n; i++ {
			op = entries[i].op
			errno = 0
			qty = 0
			if stdcall5(_WSAGetOverlappedResult, op.pd.fd, uintptr(unsafe.Pointer(op)), uintptr(unsafe.Pointer(&qty)), 0, uintptr(unsafe.Pointer(&flags))) == 0 {
				errno = int32(getlasterror())
			}
			handlecompletion(&gp, op, errno, qty)
		}
	} else {
		op = nil
		errno = 0
		qty = 0
		if block {
			mp.blocked = true
		}
		if stdcall5(_GetQueuedCompletionStatus, iocphandle, uintptr(unsafe.Pointer(&qty)), uintptr(unsafe.Pointer(&key)), uintptr(unsafe.Pointer(&op)), uintptr(wait)) == 0 {
			mp.blocked = false
			errno = int32(getlasterror())
			if !block && errno == _WAIT_TIMEOUT {
				return nil
			}
			if op == nil {
				println("netpoll: GetQueuedCompletionStatus failed (errno=", errno, ")")
				gothrow("netpoll: GetQueuedCompletionStatus failed")
			}
			// dequeued failed IO packet, so report that
		}
		mp.blocked = false
		handlecompletion(&gp, op, errno, qty)
	}
	if block && gp == nil {
		goto retry
	}
	return gp
}

func handlecompletion(gpp **g, op *net_op, errno int32, qty uint32) {
	if op == nil {
		gothrow("netpoll: GetQueuedCompletionStatus returned op == nil")
	}
	mode := op.mode
	if mode != 'r' && mode != 'w' {
		println("netpoll: GetQueuedCompletionStatus returned invalid mode=", mode)
		gothrow("netpoll: GetQueuedCompletionStatus returned invalid mode")
	}
	op.errno = errno
	op.qty = qty
	netpollready(gpp, op.pd, mode)
}
<|endoftext|>


<commit_before>package pgx

import (
	"bufio"
	"encoding/binary"
	"errors"
	"io"
	"io/ioutil"
)

// msgReader is a helper that reads values from a PostgreSQL message.
type msgReader struct {
	reader            *bufio.Reader
	buf               [128]byte
	msgBytesRemaining int32
	err               error
	log               func(lvl int, msg string, ctx ...interface{})
	shouldLog         func(lvl int) bool
}

// Err returns any error that the msgReader has experienced
func (r *msgReader) Err() error {
	return r.err
}

// fatal tells r that a Fatal error has occurred
func (r *msgReader) fatal(err error) {
	if r.shouldLog(LogLevelTrace) {
		r.log(LogLevelTrace, "msgReader.fatal", "error", err, "msgBytesRemaining", r.msgBytesRemaining)
	}
	r.err = err
}

// rxMsg reads the type and size of the next message.
func (r *msgReader) rxMsg() (byte, error) {
	if r.err != nil {
		return 0, r.err
	}

	if r.msgBytesRemaining > 0 {
		if r.shouldLog(LogLevelTrace) {
			r.log(LogLevelTrace, "msgReader.rxMsg discarding unread previous message", "msgBytesRemaining", r.msgBytesRemaining)
		}

		io.CopyN(ioutil.Discard, r.reader, int64(r.msgBytesRemaining))
	}

	b := r.buf[0:5]
	_, err := io.ReadFull(r.reader, b)
	r.msgBytesRemaining = int32(binary.BigEndian.Uint32(b[1:])) - 4
	return b[0], err
}

func (r *msgReader) readByte() byte {
	if r.err != nil {
		return 0
	}

	r.msgBytesRemaining -= 1
	if r.msgBytesRemaining < 0 {
		r.fatal(errors.New("read past end of message"))
		return 0
	}

	b, err := r.reader.ReadByte()
	if err != nil {
		r.fatal(err)
		return 0
	}

	if r.shouldLog(LogLevelTrace) {
		r.log(LogLevelTrace, "msgReader.readByte", "value", b, "byteAsString", string(b), "msgBytesRemaining", r.msgBytesRemaining)
	}

	return b
}

func (r *msgReader) readInt16() int16 {
	if r.err != nil {
		return 0
	}

	r.msgBytesRemaining -= 2
	if r.msgBytesRemaining < 0 {
		r.fatal(errors.New("read past end of message"))
		return 0
	}

	b := r.buf[0:2]
	_, err := io.ReadFull(r.reader, b)
	if err != nil {
		r.fatal(err)
		return 0
	}

	n := int16(binary.BigEndian.Uint16(b))

	if r.shouldLog(LogLevelTrace) {
		r.log(LogLevelTrace, "msgReader.readInt16", "value", n, "msgBytesRemaining", r.msgBytesRemaining)
	}

	return n
}

func (r *msgReader) readInt32() int32 {
	if r.err != nil {
		return 0
	}

	r.msgBytesRemaining -= 4
	if r.msgBytesRemaining < 0 {
		r.fatal(errors.New("read past end of message"))
		return 0
	}

	b := r.buf[0:4]
	_, err := io.ReadFull(r.reader, b)
	if err != nil {
		r.fatal(err)
		return 0
	}

	n := int32(binary.BigEndian.Uint32(b))

	if r.shouldLog(LogLevelTrace) {
		r.log(LogLevelTrace, "msgReader.readInt32", "value", n, "msgBytesRemaining", r.msgBytesRemaining)
	}

	return n
}

func (r *msgReader) readInt64() int64 {
	if r.err != nil {
		return 0
	}

	r.msgBytesRemaining -= 8
	if r.msgBytesRemaining < 0 {
		r.fatal(errors.New("read past end of message"))
		return 0
	}

	b := r.buf[0:8]
	_, err := io.ReadFull(r.reader, b)
	if err != nil {
		r.fatal(err)
		return 0
	}

	n := int64(binary.BigEndian.Uint64(b))

	if r.shouldLog(LogLevelTrace) {
		r.log(LogLevelTrace, "msgReader.readInt64", "value", n, "msgBytesRemaining", r.msgBytesRemaining)
	}

	return n
}

func (r *msgReader) readOid() Oid {
	return Oid(r.readInt32())
}

// readCString reads a null terminated string
func (r *msgReader) readCString() string {
	if r.err != nil {
		return ""
	}

	b, err := r.reader.ReadBytes(0)
	if err != nil {
		r.fatal(err)
		return ""
	}

	r.msgBytesRemaining -= int32(len(b))
	if r.msgBytesRemaining < 0 {
		r.fatal(errors.New("read past end of message"))
		return ""
	}

	s := string(b[0 : len(b)-1])

	if r.shouldLog(LogLevelTrace) {
		r.log(LogLevelTrace, "msgReader.readCString", "value", s, "msgBytesRemaining", r.msgBytesRemaining)
	}

	return s
}

// readString reads count bytes and returns as string
func (r *msgReader) readString(count int32) string {
	if r.err != nil {
		return ""
	}

	r.msgBytesRemaining -= count
	if r.msgBytesRemaining < 0 {
		r.fatal(errors.New("read past end of message"))
		return ""
	}

	var b []byte
	if count <= int32(len(r.buf)) {
		b = r.buf[0:int(count)]
	} else {
		b = make([]byte, int(count))
	}

	_, err := io.ReadFull(r.reader, b)
	if err != nil {
		r.fatal(err)
		return ""
	}

	s := string(b)

	if r.shouldLog(LogLevelTrace) {
		r.log(LogLevelTrace, "msgReader.readString", "value", s, "msgBytesRemaining", r.msgBytesRemaining)
	}

	return s
}

// readBytes reads count bytes and returns as []byte
func (r *msgReader) readBytes(count int32) []byte {
	if r.err != nil {
		return nil
	}

	r.msgBytesRemaining -= count
	if r.msgBytesRemaining < 0 {
		r.fatal(errors.New("read past end of message"))
		return nil
	}

	b := make([]byte, int(count))

	_, err := io.ReadFull(r.reader, b)
	if err != nil {
		r.fatal(err)
		return nil
	}

	if r.shouldLog(LogLevelTrace) {
		r.log(LogLevelTrace, "msgReader.readBytes", "value", b, "msgBytesRemaining", r.msgBytesRemaining)
	}

	return b
}
<commit_msg>Use bufio.Reader.Discard instead of CopyN to ioutil.Discard<commit_after>package pgx

import (
	"bufio"
	"encoding/binary"
	"errors"
	"io"
)

// msgReader is a helper that reads values from a PostgreSQL message.
type msgReader struct {
	reader            *bufio.Reader
	buf               [128]byte
	msgBytesRemaining int32
	err               error
	log               func(lvl int, msg string, ctx ...interface{})
	shouldLog         func(lvl int) bool
}

// Err returns any error that the msgReader has experienced
func (r *msgReader) Err() error {
	return r.err
}

// fatal tells r that a Fatal error has occurred
func (r *msgReader) fatal(err error) {
	if r.shouldLog(LogLevelTrace) {
		r.log(LogLevelTrace, "msgReader.fatal", "error", err, "msgBytesRemaining", r.msgBytesRemaining)
	}
	r.err = err
}

// rxMsg reads the type and size of the next message.
func (r *msgReader) rxMsg() (byte, error) {
	if r.err != nil {
		return 0, r.err
	}

	if r.msgBytesRemaining > 0 {
		if r.shouldLog(LogLevelTrace) {
			r.log(LogLevelTrace, "msgReader.rxMsg discarding unread previous message", "msgBytesRemaining", r.msgBytesRemaining)
		}

		_, err := r.reader.Discard(int(r.msgBytesRemaining))
		if err != nil {
			return 0, err
		}
	}

	b := r.buf[0:5]
	_, err := io.ReadFull(r.reader, b)
	r.msgBytesRemaining = int32(binary.BigEndian.Uint32(b[1:])) - 4
	return b[0], err
}

func (r *msgReader) readByte() byte {
	if r.err != nil {
		return 0
	}

	r.msgBytesRemaining -= 1
	if r.msgBytesRemaining < 0 {
		r.fatal(errors.New("read past end of message"))
		return 0
	}

	b, err := r.reader.ReadByte()
	if err != nil {
		r.fatal(err)
		return 0
	}

	if r.shouldLog(LogLevelTrace) {
		r.log(LogLevelTrace, "msgReader.readByte", "value", b, "byteAsString", string(b), "msgBytesRemaining", r.msgBytesRemaining)
	}

	return b
}

func (r *msgReader) readInt16() int16 {
	if r.err != nil {
		return 0
	}

	r.msgBytesRemaining -= 2
	if r.msgBytesRemaining < 0 {
		r.fatal(errors.New("read past end of message"))
		return 0
	}

	b := r.buf[0:2]
	_, err := io.ReadFull(r.reader, b)
	if err != nil {
		r.fatal(err)
		return 0
	}

	n := int16(binary.BigEndian.Uint16(b))

	if r.shouldLog(LogLevelTrace) {
		r.log(LogLevelTrace, "msgReader.readInt16", "value", n, "msgBytesRemaining", r.msgBytesRemaining)
	}

	return n
}

func (r *msgReader) readInt32() int32 {
	if r.err != nil {
		return 0
	}

	r.msgBytesRemaining -= 4
	if r.msgBytesRemaining < 0 {
		r.fatal(errors.New("read past end of message"))
		return 0
	}

	b := r.buf[0:4]
	_, err := io.ReadFull(r.reader, b)
	if err != nil {
		r.fatal(err)
		return 0
	}

	n := int32(binary.BigEndian.Uint32(b))

	if r.shouldLog(LogLevelTrace) {
		r.log(LogLevelTrace, "msgReader.readInt32", "value", n, "msgBytesRemaining", r.msgBytesRemaining)
	}

	return n
}

func (r *msgReader) readInt64() int64 {
	if r.err != nil {
		return 0
	}

	r.msgBytesRemaining -= 8
	if r.msgBytesRemaining < 0 {
		r.fatal(errors.New("read past end of message"))
		return 0
	}

	b := r.buf[0:8]
	_, err := io.ReadFull(r.reader, b)
	if err != nil {
		r.fatal(err)
		return 0
	}

	n := int64(binary.BigEndian.Uint64(b))

	if r.shouldLog(LogLevelTrace) {
		r.log(LogLevelTrace, "msgReader.readInt64", "value", n, "msgBytesRemaining", r.msgBytesRemaining)
	}

	return n
}

func (r *msgReader) readOid() Oid {
	return Oid(r.readInt32())
}

// readCString reads a null terminated string
func (r *msgReader) readCString() string {
	if r.err != nil {
		return ""
	}

	b, err := r.reader.ReadBytes(0)
	if err != nil {
		r.fatal(err)
		return ""
	}

	r.msgBytesRemaining -= int32(len(b))
	if r.msgBytesRemaining < 0 {
		r.fatal(errors.New("read past end of message"))
		return ""
	}

	s := string(b[0 : len(b)-1])

	if r.shouldLog(LogLevelTrace) {
		r.log(LogLevelTrace, "msgReader.readCString", "value", s, "msgBytesRemaining", r.msgBytesRemaining)
	}

	return s
}

// readString reads count bytes and returns as string
func (r *msgReader) readString(count int32) string {
	if r.err != nil {
		return ""
	}

	r.msgBytesRemaining -= count
	if r.msgBytesRemaining < 0 {
		r.fatal(errors.New("read past end of message"))
		return ""
	}

	var b []byte
	if count <= int32(len(r.buf)) {
		b = r.buf[0:int(count)]
	} else {
		b = make([]byte, int(count))
	}

	_, err := io.ReadFull(r.reader, b)
	if err != nil {
		r.fatal(err)
		return ""
	}

	s := string(b)

	if r.shouldLog(LogLevelTrace) {
		r.log(LogLevelTrace, "msgReader.readString", "value", s, "msgBytesRemaining", r.msgBytesRemaining)
	}

	return s
}

// readBytes reads count bytes and returns as []byte
func (r *msgReader) readBytes(count int32) []byte {
	if r.err != nil {
		return nil
	}

	r.msgBytesRemaining -= count
	if r.msgBytesRemaining < 0 {
		r.fatal(errors.New("read past end of message"))
		return nil
	}

	b := make([]byte, int(count))

	_, err := io.ReadFull(r.reader, b)
	if err != nil {
		r.fatal(err)
		return nil
	}

	if r.shouldLog(LogLevelTrace) {
		r.log(LogLevelTrace, "msgReader.readBytes", "value", b, "msgBytesRemaining", r.msgBytesRemaining)
	}

	return b
}
<|endoftext|>


<commit_before>package mssql

import (
	"context"
	"testing"
)

func TestBadOpen(t *testing.T) {
	drv := driverWithProcess(t)
	_, err := drv.open(context.Background(), "port=bad")
	if err == nil {
		t.Fail()
	}
}

func TestIsProc(t *testing.T) {
	list := []struct {
		s  string
		is bool
	}{
		{"proc", true},
		{"select 1;", false},
		{"[proc 1]", true},
		{"[proc\n1]", false},
	}

	for _, item := range list {
		got := isProc(item.s)
		if got != item.is {
			t.Errorf("for %q, got %t want %t", item.s, got, item.is)
		}
	}
}
<commit_msg>mssql: add additional tests to TestIsProc clarify what is supported<commit_after>package mssql

import (
	"context"
	"testing"
)

func TestBadOpen(t *testing.T) {
	drv := driverWithProcess(t)
	_, err := drv.open(context.Background(), "port=bad")
	if err == nil {
		t.Fail()
	}
}

func TestIsProc(t *testing.T) {
	list := []struct {
		s  string
		is bool
	}{
		{"proc", true},
		{"select 1;", false},
		{"[proc 1]", true},
		{"[proc\n1]", false},
		{"schema.name", true},
		{"[schema].[name]", true},
		{"schema.[name]", true},
		{"[schema].name", true},
	}

	for _, item := range list {
		got := isProc(item.s)
		if got != item.is {
			t.Errorf("for %q, got %t want %t", item.s, got, item.is)
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2011 Google Inc. All Rights Reserved.
// This file is available under the Apache license.

package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"runtime"
	"strings"
	"testing"
	"time"

	"github.com/google/mtail/vm"
	"github.com/google/mtail/watcher"
)

const testProgram = "/$/ { }"

func startMtail(t *testing.T, logPathnames []string, progPathname string) *mtail {
	m := newMtail()
	w, err := watcher.NewLogWatcher()
	if err != nil {
		t.Fatalf("Couldn't create watcher: %s", err)
	}
	if m.l = vm.NewLoader(w, &m.store, m.lines); m.l == nil {
		t.Fatalf("Couldn't create program loader.")
	}

	if progPathname != "" {
		m.l.LoadProgs(progPathname)
	} else {
		if pErr := m.l.CompileAndRun("test", strings.NewReader(testProgram)); pErr != nil {
			t.Errorf("Couldn't compile program: %s", pErr)
		}
	}

	vm.LineCount.Set(0)

	m.StartTailing(logPathnames)
	return m
}

func TestHandleLogUpdates(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode")
	}
	workdir := makeTempDir(t)
	defer removeTempDir(t, workdir)
	// touch log file
	logFilepath := path.Join(workdir, "log")
	logFile, err := os.Create(logFilepath)
	if err != nil {
		t.Errorf("could not touch log file: %s", err)
	}
	defer logFile.Close()
	pathnames := []string{logFilepath}
	m := startMtail(t, pathnames, "")
	defer m.Close()
	inputLines := []string{"hi", "hi2", "hi3"}
	for i, x := range inputLines {
		t.Logf("string is %q", x)
		// write to log file
		logFile.WriteString(x + "\n")
		// TODO(jaq): remove slow sleep
		time.Sleep(1 * time.Second)
		// check log line count increase
		expected := fmt.Sprintf("%d", i+1)
		if vm.LineCount.String() != expected {
			t.Errorf("Line count not increased\n\texpected: %s\n\treceived: %s", expected, vm.LineCount.String())
			buf := make([]byte, 1<<16)
			runtime.Stack(buf, true)
			fmt.Printf("%s", buf)
		}
	}
}

func TestHandleLogRotation(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode")
	}
	workdir := makeTempDir(t)
	defer removeTempDir(t, workdir)
	logFilepath := path.Join(workdir, "log")
	// touch log file
	logFile, err := os.Create(logFilepath)
	if err != nil {
		t.Errorf("could not touch log file: %s", err)
	}
	defer logFile.Close()
	// Create a logger
	stop := make(chan bool, 1)
	hup := make(chan bool, 1)
	pathnames := []string{logFilepath}
	m := startMtail(t, pathnames, "")
	defer m.Close()

	go func() {
		logFile := logFile
		var err error
		i := 0
		running := true
		for running {
			select {
			case <-hup:
				// touch log file
				logFile, err = os.Create(logFilepath)
				if err != nil {
					t.Errorf("could not touch log file: %s", err)
				}
				defer logFile.Close()
			default:
				logFile.WriteString(fmt.Sprintf("%d\n", i))
				time.Sleep(100 * time.Millisecond)
				i++
				if i >= 10 {
					running = false
				}
			}
		}
		stop <- true
	}()
	go func() {
		for {
			select {
			case <-time.After(5 * 100 * time.Millisecond):
				err = os.Rename(logFilepath, logFilepath+".1")
				if err != nil {
					t.Errorf("could not rename log file: %s", err)
				}
				hup <- true
				return
			}
		}
	}()
	<-stop
	expected := "10"
	if vm.LineCount.String() != expected {
		t.Errorf("Line count not increased\n\texpected: %s\n\treceived: %s", expected, vm.LineCount.String())
	}
}

func TestHandleNewLogAfterStart(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode")
	}

	workdir := makeTempDir(t)
	defer removeTempDir(t, workdir)
	// Start up mtail
	logFilepath := path.Join(workdir, "log")
	pathnames := []string{logFilepath}
	m := startMtail(t, pathnames, "")
	defer m.Close()

	// touch log file
	logFile, err := os.Create(logFilepath)
	if err != nil {
		t.Errorf("could not touch log file: %s", err)
	}
	defer logFile.Close()
	inputLines := []string{"hi", "hi2", "hi3"}
	for _, x := range inputLines {
		// write to log file
		logFile.WriteString(x + "\n")
		logFile.Sync()
	}
	// TODO(jaq): remove slow sleep
	time.Sleep(100 * time.Millisecond)
	// check log line count increase
	expected := fmt.Sprintf("%d", len(inputLines))
	if vm.LineCount.String() != expected {
		t.Errorf("Line count not increased\n\texpected: %s\n\treceived: %s", expected, vm.LineCount.String())
	}
}

func TestHandleNewLogIgnored(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode")
	}
	workdir := makeTempDir(t)
	defer removeTempDir(t, workdir)
	// Start mtail
	logFilepath := path.Join(workdir, "log")
	pathnames := []string{logFilepath}
	m := startMtail(t, pathnames, "")
	defer m.Close()

	// touch log file
	newLogFilepath := path.Join(workdir, "log1")

	logFile, err := os.Create(newLogFilepath)
	if err != nil {
		t.Errorf("could not touch log file: %s", err)
	}
	defer logFile.Close()
	expected := "0"
	if vm.LineCount.String() != expected {
		t.Errorf("Line count not increased\n\texpected: %s\n\treceived: %s", expected, vm.LineCount.String())
	}
}

func makeTempDir(t *testing.T) (workdir string) {
	var err error
	if workdir, err = ioutil.TempDir("", "mtail_test"); err != nil {
		t.Fatalf("ioutil.TempDir failed: %s", err)
	}
	return
}

func removeTempDir(t *testing.T, workdir string) {
	if err := os.RemoveAll(workdir); err != nil {
		t.Fatalf("os.RemoveAll failed: %s", err)
	}
}

// TODO(jaq): The sleeps in here are racy.  What can we use to sync through inotify?
func TestHandleNewProgram(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode")
	}

	workdir := makeTempDir(t)
	defer removeTempDir(t, workdir)

	m := startMtail(t, []string{}, workdir)
	defer m.Close()

	expectedProgLoads := "{}"
	if vm.ProgLoads.String() != expectedProgLoads {
		t.Errorf("Prog loads not same\n\texpected: %s\n\treceived: %s", expectedProgLoads, vm.ProgLoads.String())
	}

	progPath := path.Join(workdir, "prog.mtail")
	progFile, err := os.Create(progPath)
	if err != nil {
		t.Fatalf("prog create failed: %s", err)
	}
	progFile.WriteString("/$/ {}\n")
	progFile.Close()

	// Wait for inotify
	time.Sleep(100 * time.Millisecond)
	expectedProgLoads = `{"prog.mtail": 1}`
	if vm.ProgLoads.String() != expectedProgLoads {
		t.Errorf("Prog loads not same\n\texpected: %s\n\treceived: %s", expectedProgLoads, vm.ProgLoads.String())
	}

	badProgPath := path.Join(workdir, "prog.mtail.dpkg-dist")
	badProgFile, err := os.Create(badProgPath)
	if err != nil {
		t.Fatalf("prog create failed: %s", err)
	}
	badProgFile.WriteString("/$/ {}\n")
	badProgFile.Close()

	time.Sleep(100 * time.Millisecond)
	expectedProgLoads = `{"prog.mtail": 1}`
	if vm.ProgLoads.String() != expectedProgLoads {
		t.Errorf("Prog loads not same\n\texpected: %s\n\treceived: %s", expectedProgLoads, vm.ProgLoads.String())
	}
	expectedProgErrs := `{}`
	if vm.ProgLoadErrors.String() != expectedProgErrs {
		t.Errorf("Prog errors not same\n\texpected: %s\n\treceived: %s", expectedProgErrs, vm.ProgLoadErrors.String())
	}

	os.Rename(badProgPath, progPath)
	time.Sleep(100 * time.Millisecond)
	expectedProgLoads = `{"prog.mtail": 1}`
	if vm.ProgLoads.String() != expectedProgLoads {
		t.Errorf("Prog loads not same\n\texpected: %s\n\treceived: %s", expectedProgLoads, vm.ProgLoads.String())
	}
	expectedProgErrs = `{}`
	if vm.ProgLoadErrors.String() != expectedProgErrs {
		t.Errorf("Prog errors not same\n\texpected: %s\n\treceived: %s", expectedProgErrs, vm.ProgLoadErrors.String())
	}

	brokenProgPath := path.Join(workdir, "broken.mtail")
	brokenProgFile, err := os.Create(brokenProgPath)
	if err != nil {
		t.Errorf("prog create failed: %s", err)
	}
	brokenProgFile.WriteString("?\n")
	brokenProgFile.Close()

	time.Sleep(100 * time.Millisecond)

	expectedProgLoads = `{"prog.mtail": 1}`
	if vm.ProgLoads.String() != expectedProgLoads {
		t.Errorf("Prog loads not same\n\texpected: %s\n\treceived: %s", expectedProgLoads, vm.ProgLoads.String())
	}
	expectedProgErrs = `{"broken.mtail": 1}`
	if vm.ProgLoadErrors.String() != expectedProgErrs {
		t.Errorf("Prog errors not same\n\texpected: %s\n\treceived: %s", expectedProgErrs, vm.ProgLoadErrors.String())
	}

}
<commit_msg>Move setup/teardown helpers to top of file.<commit_after>// Copyright 2011 Google Inc. All Rights Reserved.
// This file is available under the Apache license.

package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"runtime"
	"strings"
	"testing"
	"time"

	"github.com/google/mtail/vm"
	"github.com/google/mtail/watcher"
)

const testProgram = "/$/ { }"

func makeTempDir(t *testing.T) (workdir string) {
	var err error
	if workdir, err = ioutil.TempDir("", "mtail_test"); err != nil {
		t.Fatalf("ioutil.TempDir failed: %s", err)
	}
	return
}

func removeTempDir(t *testing.T, workdir string) {
	if err := os.RemoveAll(workdir); err != nil {
		t.Fatalf("os.RemoveAll failed: %s", err)
	}
}

func startMtail(t *testing.T, logPathnames []string, progPathname string) *mtail {
	m := newMtail()
	w, err := watcher.NewLogWatcher()
	if err != nil {
		t.Fatalf("Couldn't create watcher: %s", err)
	}
	if m.l = vm.NewLoader(w, &m.store, m.lines); m.l == nil {
		t.Fatalf("Couldn't create program loader.")
	}

	if progPathname != "" {
		m.l.LoadProgs(progPathname)
	} else {
		if pErr := m.l.CompileAndRun("test", strings.NewReader(testProgram)); pErr != nil {
			t.Errorf("Couldn't compile program: %s", pErr)
		}
	}

	vm.LineCount.Set(0)

	m.StartTailing(logPathnames)
	return m
}

func TestHandleLogUpdates(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode")
	}
	workdir := makeTempDir(t)
	defer removeTempDir(t, workdir)
	// touch log file
	logFilepath := path.Join(workdir, "log")
	logFile, err := os.Create(logFilepath)
	if err != nil {
		t.Errorf("could not touch log file: %s", err)
	}
	defer logFile.Close()
	pathnames := []string{logFilepath}
	m := startMtail(t, pathnames, "")
	defer m.Close()
	inputLines := []string{"hi", "hi2", "hi3"}
	for i, x := range inputLines {
		t.Logf("string is %q", x)
		// write to log file
		logFile.WriteString(x + "\n")
		// TODO(jaq): remove slow sleep
		time.Sleep(1 * time.Second)
		// check log line count increase
		expected := fmt.Sprintf("%d", i+1)
		if vm.LineCount.String() != expected {
			t.Errorf("Line count not increased\n\texpected: %s\n\treceived: %s", expected, vm.LineCount.String())
			buf := make([]byte, 1<<16)
			runtime.Stack(buf, true)
			fmt.Printf("%s", buf)
		}
	}
}

func TestHandleLogRotation(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode")
	}
	workdir := makeTempDir(t)
	defer removeTempDir(t, workdir)
	logFilepath := path.Join(workdir, "log")
	// touch log file
	logFile, err := os.Create(logFilepath)
	if err != nil {
		t.Errorf("could not touch log file: %s", err)
	}
	defer logFile.Close()
	// Create a logger
	stop := make(chan bool, 1)
	hup := make(chan bool, 1)
	pathnames := []string{logFilepath}
	m := startMtail(t, pathnames, "")
	defer m.Close()

	go func() {
		logFile := logFile
		var err error
		i := 0
		running := true
		for running {
			select {
			case <-hup:
				// touch log file
				logFile, err = os.Create(logFilepath)
				if err != nil {
					t.Errorf("could not touch log file: %s", err)
				}
				defer logFile.Close()
			default:
				logFile.WriteString(fmt.Sprintf("%d\n", i))
				time.Sleep(100 * time.Millisecond)
				i++
				if i >= 10 {
					running = false
				}
			}
		}
		stop <- true
	}()
	go func() {
		for {
			select {
			case <-time.After(5 * 100 * time.Millisecond):
				err = os.Rename(logFilepath, logFilepath+".1")
				if err != nil {
					t.Errorf("could not rename log file: %s", err)
				}
				hup <- true
				return
			}
		}
	}()
	<-stop
	expected := "10"
	if vm.LineCount.String() != expected {
		t.Errorf("Line count not increased\n\texpected: %s\n\treceived: %s", expected, vm.LineCount.String())
	}
}

func TestHandleNewLogAfterStart(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode")
	}

	workdir := makeTempDir(t)
	defer removeTempDir(t, workdir)
	// Start up mtail
	logFilepath := path.Join(workdir, "log")
	pathnames := []string{logFilepath}
	m := startMtail(t, pathnames, "")
	defer m.Close()

	// touch log file
	logFile, err := os.Create(logFilepath)
	if err != nil {
		t.Errorf("could not touch log file: %s", err)
	}
	defer logFile.Close()
	inputLines := []string{"hi", "hi2", "hi3"}
	for _, x := range inputLines {
		// write to log file
		logFile.WriteString(x + "\n")
		logFile.Sync()
	}
	// TODO(jaq): remove slow sleep
	time.Sleep(100 * time.Millisecond)
	// check log line count increase
	expected := fmt.Sprintf("%d", len(inputLines))
	if vm.LineCount.String() != expected {
		t.Errorf("Line count not increased\n\texpected: %s\n\treceived: %s", expected, vm.LineCount.String())
	}
}

func TestHandleNewLogIgnored(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode")
	}
	workdir := makeTempDir(t)
	defer removeTempDir(t, workdir)
	// Start mtail
	logFilepath := path.Join(workdir, "log")
	pathnames := []string{logFilepath}
	m := startMtail(t, pathnames, "")
	defer m.Close()

	// touch log file
	newLogFilepath := path.Join(workdir, "log1")

	logFile, err := os.Create(newLogFilepath)
	if err != nil {
		t.Errorf("could not touch log file: %s", err)
	}
	defer logFile.Close()
	expected := "0"
	if vm.LineCount.String() != expected {
		t.Errorf("Line count not increased\n\texpected: %s\n\treceived: %s", expected, vm.LineCount.String())
	}
}

// TODO(jaq): The sleeps in here are racy.  What can we use to sync through inotify?
func TestHandleNewProgram(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode")
	}

	workdir := makeTempDir(t)
	defer removeTempDir(t, workdir)

	m := startMtail(t, []string{}, workdir)
	defer m.Close()

	expectedProgLoads := "{}"
	if vm.ProgLoads.String() != expectedProgLoads {
		t.Errorf("Prog loads not same\n\texpected: %s\n\treceived: %s", expectedProgLoads, vm.ProgLoads.String())
	}

	progPath := path.Join(workdir, "prog.mtail")
	progFile, err := os.Create(progPath)
	if err != nil {
		t.Fatalf("prog create failed: %s", err)
	}
	progFile.WriteString("/$/ {}\n")
	progFile.Close()

	// Wait for inotify
	time.Sleep(100 * time.Millisecond)
	expectedProgLoads = `{"prog.mtail": 1}`
	if vm.ProgLoads.String() != expectedProgLoads {
		t.Errorf("Prog loads not same\n\texpected: %s\n\treceived: %s", expectedProgLoads, vm.ProgLoads.String())
	}

	badProgPath := path.Join(workdir, "prog.mtail.dpkg-dist")
	badProgFile, err := os.Create(badProgPath)
	if err != nil {
		t.Fatalf("prog create failed: %s", err)
	}
	badProgFile.WriteString("/$/ {}\n")
	badProgFile.Close()

	time.Sleep(100 * time.Millisecond)
	expectedProgLoads = `{"prog.mtail": 1}`
	if vm.ProgLoads.String() != expectedProgLoads {
		t.Errorf("Prog loads not same\n\texpected: %s\n\treceived: %s", expectedProgLoads, vm.ProgLoads.String())
	}
	expectedProgErrs := `{}`
	if vm.ProgLoadErrors.String() != expectedProgErrs {
		t.Errorf("Prog errors not same\n\texpected: %s\n\treceived: %s", expectedProgErrs, vm.ProgLoadErrors.String())
	}

	os.Rename(badProgPath, progPath)
	time.Sleep(100 * time.Millisecond)
	expectedProgLoads = `{"prog.mtail": 1}`
	if vm.ProgLoads.String() != expectedProgLoads {
		t.Errorf("Prog loads not same\n\texpected: %s\n\treceived: %s", expectedProgLoads, vm.ProgLoads.String())
	}
	expectedProgErrs = `{}`
	if vm.ProgLoadErrors.String() != expectedProgErrs {
		t.Errorf("Prog errors not same\n\texpected: %s\n\treceived: %s", expectedProgErrs, vm.ProgLoadErrors.String())
	}

	brokenProgPath := path.Join(workdir, "broken.mtail")
	brokenProgFile, err := os.Create(brokenProgPath)
	if err != nil {
		t.Errorf("prog create failed: %s", err)
	}
	brokenProgFile.WriteString("?\n")
	brokenProgFile.Close()

	time.Sleep(100 * time.Millisecond)

	expectedProgLoads = `{"prog.mtail": 1}`
	if vm.ProgLoads.String() != expectedProgLoads {
		t.Errorf("Prog loads not same\n\texpected: %s\n\treceived: %s", expectedProgLoads, vm.ProgLoads.String())
	}
	expectedProgErrs = `{"broken.mtail": 1}`
	if vm.ProgLoadErrors.String() != expectedProgErrs {
		t.Errorf("Prog errors not same\n\texpected: %s\n\treceived: %s", expectedProgErrs, vm.ProgLoadErrors.String())
	}
}
<|endoftext|>


<commit_before>/*

*/
package mandrake

import (
	"fmt"
	"log"
	"encoding/json"

	"golang.org/x/exp/inotify"
	"github.com/hosom/gomandrake/config"
	"github.com/hosom/gomandrake/filemeta"
	"github.com/hosom/gomandrake/plugin"
)

// Mandrake is a wrapper struct for the bulk of the application logic
type Mandrake struct {
	AnalysisPipeline	chan string
	MonitoredDirectory	string
	Analyzers			[]plugin.AnalyzerCaller
}

// NewMandrake creates and returns a Mandrake struct utilizing a passed
// parsed configuration file to create the correct fields.
func NewMandrake(c config.Config) Mandrake {
	analyzers := []plugin.AnalyzerCaller{}
	for _, plug := range c.Analyzers {
		analyzer := plugin.NewAnalyzerCaller(plug)
		analyzers = append(analyzers, analyzer)
	}

	return Mandrake{make(chan string), c.MonitoredDirectory, analyzers}
}

// ListenAndServe starts the goroutines that perform all of the heavy lifting
// including Monitor() and DispatchAnalysis().
func (m Mandrake) ListenAndServe() {
	log.SetPrefix("[mandrake] ")
	log.Println(m.Analyzers[0])
	go m.DispatchAnalysis()
	m.Monitor()
}

// DispatchAnalysis intelligently sends a new file to registered plugins so
// that it can be analyzed.
func (m Mandrake) DispatchAnalysis() {
	for fpath := range m.AnalysisPipeline {
		fmeta, err := filemeta.NewFileMeta(fpath)
		if err != nil {
			log.Println(err)
		}

		fs, err := json.Marshal(fmeta)

		for _, analyzer := range m.Analyzers {
			result, err := analyzer.Analyze(fmeta)
			log.Print(result)
		}

		log.Println(string(fs))
		log.Printf("%s", fpath)
	}
}

// Monitor uses inotify to monitor the MonitoredDirectory for IN_CLOSE_WRITE
// events. Files written to the MonitoredDirectory will be sent to the
// analysis pipeline to be analyzed.
func (m Mandrake) Monitor() {
	log.Println("starting inotify watcher")
	watcher, err := inotify.NewWatcher()
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("adding watcher to %s directory", m.MonitoredDirectory)
	err = watcher.AddWatch(m.MonitoredDirectory, inotify.IN_CLOSE_WRITE)
	if err != nil {
		log.Fatal(err)
	}

	for {
		select {
		case ev := <- watcher.Event:
			m.AnalysisPipeline <- ev.Name
		case err := <- watcher.Error:
			log.Printf("inotify error: %s", err)
		}
	}
}<commit_msg>add Analyze call for plugins<commit_after>/*

*/
package mandrake

import (
	"fmt"
	"log"
	"encoding/json"

	"golang.org/x/exp/inotify"
	"github.com/hosom/gomandrake/config"
	"github.com/hosom/gomandrake/filemeta"
	"github.com/hosom/gomandrake/plugin"
)

// Mandrake is a wrapper struct for the bulk of the application logic
type Mandrake struct {
	AnalysisPipeline	chan string
	MonitoredDirectory	string
	Analyzers			[]plugin.AnalyzerCaller
}

// NewMandrake creates and returns a Mandrake struct utilizing a passed
// parsed configuration file to create the correct fields.
func NewMandrake(c config.Config) Mandrake {
	analyzers := []plugin.AnalyzerCaller{}
	for _, plug := range c.Analyzers {
		analyzer := plugin.NewAnalyzerCaller(plug)
		analyzers = append(analyzers, analyzer)
	}

	return Mandrake{make(chan string), c.MonitoredDirectory, analyzers}
}

// ListenAndServe starts the goroutines that perform all of the heavy lifting
// including Monitor() and DispatchAnalysis().
func (m Mandrake) ListenAndServe() {
	log.SetPrefix("[mandrake] ")
	log.Println(m.Analyzers[0])
	go m.DispatchAnalysis()
	m.Monitor()
}

// DispatchAnalysis intelligently sends a new file to registered plugins so
// that it can be analyzed.
func (m Mandrake) DispatchAnalysis() {
	for fpath := range m.AnalysisPipeline {
		fmeta, err := filemeta.NewFileMeta(fpath)
		if err != nil {
			log.Println(err)
		}

		fs, err := json.Marshal(fmeta)

		for _, analyzer := range m.Analyzers {
			result, err := analyzer.Analyze(fs)
			log.Print(result)
		}

		log.Println(string(fs))
		log.Printf("%s", fpath)
	}
}

// Monitor uses inotify to monitor the MonitoredDirectory for IN_CLOSE_WRITE
// events. Files written to the MonitoredDirectory will be sent to the
// analysis pipeline to be analyzed.
func (m Mandrake) Monitor() {
	log.Println("starting inotify watcher")
	watcher, err := inotify.NewWatcher()
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("adding watcher to %s directory", m.MonitoredDirectory)
	err = watcher.AddWatch(m.MonitoredDirectory, inotify.IN_CLOSE_WRITE)
	if err != nil {
		log.Fatal(err)
	}

	for {
		select {
		case ev := <- watcher.Event:
			m.AnalysisPipeline <- ev.Name
		case err := <- watcher.Error:
			log.Printf("inotify error: %s", err)
		}
	}
}<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"image"
	"image/color"
	"image/gif"
	"log"
	"os"

	"github.com/hajimehoshi/ebiten"
	"github.com/martinkirsche/wired-logic/simulation"
)

var (
	simulationImage        *image.Paletted
	currentSimulation      *simulation.Simulation
	backgroundImage        *ebiten.Image
	wireImages             []*ebiten.Image
	wasMouseButtonPressed  = false
	cursorBlinking         uint8
	cursorImage            *ebiten.Image
	oldMouseCursorPosition image.Point = image.Point{-1, -1}
	cursorPosition         image.Point = image.Point{-1, -1}
	keyStates                          = map[ebiten.Key]int{
		ebiten.KeyUp:    0,
		ebiten.KeyDown:  0,
		ebiten.KeyLeft:  0,
		ebiten.KeyRight: 0,
		ebiten.KeySpace: 0,
	}
)

func main() {
	var err error
	if cursorImage, err = ebiten.NewImage(4, 4, ebiten.FilterNearest); err != nil {
		log.Fatal(err)
	}
	cursorImage.Fill(color.White)
	var scale, width, height int
	flag.IntVar(&scale, "scale", 16, "pixel scale factor")
	flag.IntVar(&width, "width", 64, "width of the simulation")
	flag.IntVar(&height, "height", 64, "height of the simulation")
	flag.Parse()
	flag.Args()

	cursorPosition = image.Point{width / 2, height / 2}
	if flag.NArg() == 1 {
		inputFileName := flag.Arg(0)
		in, err := os.Open(inputFileName)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		gifImage, err := gif.DecodeAll(in)
		if err != nil {
			log.Fatal(err)
		}
		simulationImage = gifImage.Image[0]
		simulationImage.Palette[0] = color.Transparent
	} else {
		p := color.Palette{
			color.Black,
			color.RGBA{0x88, 0, 0, 0xFF},
			color.RGBA{0xFF, 0, 0, 0xFF},
			color.RGBA{0xFF, 0x22, 0, 0xFF},
			color.RGBA{0xFF, 0x44, 0, 0xFF},
			color.RGBA{0xFF, 0x66, 0, 0xFF},
			color.RGBA{0xFF, 0x88, 0, 0xFF},
			color.RGBA{0xFF, 0xAA, 0, 0xFF},
		}
		simulationImage = image.NewPaletted(image.Rect(0, 0, width, height), p)
	}
	reloadSimulation()
	if err := ebiten.Run(update, simulationImage.Bounds().Dx(), simulationImage.Bounds().Dy(), scale, "Wired Logic"); err != nil {
		log.Fatal(err)
	}
}

func reloadSimulation() error {
	currentSimulation = simulation.New(simulationImage)
	currentSimulation.Draw(simulationImage)
	var err error
	backgroundImage, err = ebiten.NewImageFromImage(simulationImage, ebiten.FilterNearest)
	if err != nil {
		log.Fatal(err)
	}
	for _, img := range wireImages {
		if err = img.Dispose(); err != nil {
			return err
		}
	}
	wires := currentSimulation.Circuit().Wires()
	wireImages = make([]*ebiten.Image, len(wires))
	for i, wire := range wires {
		img := drawMask(wire)
		var err error
		if wireImages[i], err = ebiten.NewImageFromImage(img, ebiten.FilterNearest); err != nil {
			return err
		}
	}
	return nil
}

func togglePixel(position image.Point) error {
	currentSimulation.Draw(simulationImage)
	c := simulationImage.ColorIndexAt(position.X, position.Y)
	if c-1 > simulation.MaxCharge {
		simulationImage.SetColorIndex(position.X, position.Y, 1)
	} else {
		simulationImage.SetColorIndex(position.X, position.Y, 0)
	}
	if err := reloadSimulation(); err != nil {
		return err
	}
	return nil
}

func readKeys() {
	for key, _ := range keyStates {
		if !ebiten.IsKeyPressed(key) {
			keyStates[key] = -1
			continue
		}
		keyStates[key]++

	}
}

func handleCursor(screen *ebiten.Image) error {
	mx, my := ebiten.CursorPosition()
	cursorMoved := image.Point{mx, my}.In(screen.Bounds()) && (mx != oldMouseCursorPosition.X || my != oldMouseCursorPosition.Y)
	oldMouseCursorPosition = image.Point{mx, my}
	if cursorMoved {
		cursorPosition = oldMouseCursorPosition
	} else {
		const cursorInterval = 6
		switch {
		case keyStates[ebiten.KeyUp]%cursorInterval == 0:
			cursorPosition = cursorPosition.Add(image.Point{0, -1})
			cursorMoved = true
		case keyStates[ebiten.KeyDown]%cursorInterval == 0:
			cursorPosition = cursorPosition.Add(image.Point{0, +1})
			cursorMoved = true
		case keyStates[ebiten.KeyLeft]%cursorInterval == 0:
			cursorPosition = cursorPosition.Add(image.Point{-1, 0})
			cursorMoved = true
		case keyStates[ebiten.KeyRight]%cursorInterval == 0:
			cursorPosition = cursorPosition.Add(image.Point{+1, 0})
			cursorMoved = true
		}
	}
	if cursorBlinking == 127 {
		cursorBlinking = 0
	} else {
		cursorBlinking++
	}
	op := &ebiten.DrawImageOptions{}
	op.GeoM.Scale(0.25, .25)
	op.GeoM.Translate(float64(cursorPosition.X), float64(cursorPosition.Y))
	if cursorBlinking > 64 {
		op.ColorM.Scale(1, 1, 1, 0.25+float64(127-cursorBlinking)/255.0)
	} else {
		op.ColorM.Scale(1, 1, 1, 0.25+float64(cursorBlinking)/255.0)
	}
	if err := screen.DrawImage(cursorImage, op); err != nil {
		return err
	}
	if keyStates[ebiten.KeySpace] >= 0 ||
		ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
		if cursorMoved || !wasMouseButtonPressed {
			if err := togglePixel(cursorPosition); err != nil {
				return err
			}
			wasMouseButtonPressed = true
		}
	} else {
		wasMouseButtonPressed = false
	}
	return nil
}

func update(screen *ebiten.Image) error {
	readKeys()
	newSimulation := currentSimulation.Step()
	wires := currentSimulation.Circuit().Wires()
	for i, wire := range wires {
		oldCharge := currentSimulation.State(wire).Charge()
		charge := newSimulation.State(wire).Charge()
		if oldCharge == charge {
			continue
		}
		position := wire.Bounds().Min
		op := &ebiten.DrawImageOptions{}
		op.GeoM.Translate(float64(position.X), float64(position.Y))
		r, g, b, a := simulationImage.Palette[charge+1].RGBA()
		op.ColorM.Scale(float64(r)/0xFFFF, float64(g)/0xFFFF, float64(b)/0xFFFF, float64(a)/0xFFFF)
		var err error
		if err = backgroundImage.DrawImage(wireImages[i], op); err != nil {
			return err
		}
	}
	currentSimulation = newSimulation
	if err := screen.DrawImage(backgroundImage, &ebiten.DrawImageOptions{}); err != nil {
		return err
	}

	if err := handleCursor(screen); err != nil {
		return err
	}
	return nil
}

func drawMask(wire *simulation.Wire) image.Image {
	bounds := image.Rect(0, 0, wire.Bounds().Dx(), wire.Bounds().Dy())
	bounds = bounds.Union(image.Rect(0, 0, 4, 4))
	position := wire.Bounds().Min
	img := image.NewRGBA(bounds)
	white := color.RGBA{0xFF, 0xFF, 0xFF, 0xFF}
	for _, pixel := range wire.Pixels() {
		img.SetRGBA(pixel.X-position.X, pixel.Y-position.Y, white)
	}
	return img
}
<commit_msg>using short variable declaration for err<commit_after>package main

import (
	"flag"
	"fmt"
	"image"
	"image/color"
	"image/gif"
	"log"
	"os"

	"github.com/hajimehoshi/ebiten"
	"github.com/martinkirsche/wired-logic/simulation"
)

var (
	simulationImage        *image.Paletted
	currentSimulation      *simulation.Simulation
	backgroundImage        *ebiten.Image
	wireImages             []*ebiten.Image
	wasMouseButtonPressed  = false
	cursorBlinking         uint8
	cursorImage            *ebiten.Image
	oldMouseCursorPosition image.Point = image.Point{-1, -1}
	cursorPosition         image.Point = image.Point{-1, -1}
	keyStates                          = map[ebiten.Key]int{
		ebiten.KeyUp:    0,
		ebiten.KeyDown:  0,
		ebiten.KeyLeft:  0,
		ebiten.KeyRight: 0,
		ebiten.KeySpace: 0,
	}
)

func main() {
	var err error
	if cursorImage, err = ebiten.NewImage(4, 4, ebiten.FilterNearest); err != nil {
		log.Fatal(err)
	}
	cursorImage.Fill(color.White)
	var scale, width, height int
	flag.IntVar(&scale, "scale", 16, "pixel scale factor")
	flag.IntVar(&width, "width", 64, "width of the simulation")
	flag.IntVar(&height, "height", 64, "height of the simulation")
	flag.Parse()
	flag.Args()

	cursorPosition = image.Point{width / 2, height / 2}
	if flag.NArg() == 1 {
		inputFileName := flag.Arg(0)
		in, err := os.Open(inputFileName)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		gifImage, err := gif.DecodeAll(in)
		if err != nil {
			log.Fatal(err)
		}
		simulationImage = gifImage.Image[0]
		simulationImage.Palette[0] = color.Transparent
	} else {
		p := color.Palette{
			color.Black,
			color.RGBA{0x88, 0, 0, 0xFF},
			color.RGBA{0xFF, 0, 0, 0xFF},
			color.RGBA{0xFF, 0x22, 0, 0xFF},
			color.RGBA{0xFF, 0x44, 0, 0xFF},
			color.RGBA{0xFF, 0x66, 0, 0xFF},
			color.RGBA{0xFF, 0x88, 0, 0xFF},
			color.RGBA{0xFF, 0xAA, 0, 0xFF},
		}
		simulationImage = image.NewPaletted(image.Rect(0, 0, width, height), p)
	}
	reloadSimulation()
	if err := ebiten.Run(update, simulationImage.Bounds().Dx(), simulationImage.Bounds().Dy(), scale, "Wired Logic"); err != nil {
		log.Fatal(err)
	}
}

func reloadSimulation() error {
	currentSimulation = simulation.New(simulationImage)
	currentSimulation.Draw(simulationImage)
	var err error
	backgroundImage, err = ebiten.NewImageFromImage(simulationImage, ebiten.FilterNearest)
	if err != nil {
		log.Fatal(err)
	}
	for _, img := range wireImages {
		if err = img.Dispose(); err != nil {
			return err
		}
	}
	wires := currentSimulation.Circuit().Wires()
	wireImages = make([]*ebiten.Image, len(wires))
	for i, wire := range wires {
		img := drawMask(wire)
		var err error
		if wireImages[i], err = ebiten.NewImageFromImage(img, ebiten.FilterNearest); err != nil {
			return err
		}
	}
	return nil
}

func togglePixel(position image.Point) error {
	currentSimulation.Draw(simulationImage)
	c := simulationImage.ColorIndexAt(position.X, position.Y)
	if c-1 > simulation.MaxCharge {
		simulationImage.SetColorIndex(position.X, position.Y, 1)
	} else {
		simulationImage.SetColorIndex(position.X, position.Y, 0)
	}
	if err := reloadSimulation(); err != nil {
		return err
	}
	return nil
}

func readKeys() {
	for key, _ := range keyStates {
		if !ebiten.IsKeyPressed(key) {
			keyStates[key] = -1
			continue
		}
		keyStates[key]++

	}
}

func handleCursor(screen *ebiten.Image) error {
	mx, my := ebiten.CursorPosition()
	cursorMoved := image.Point{mx, my}.In(screen.Bounds()) && (mx != oldMouseCursorPosition.X || my != oldMouseCursorPosition.Y)
	oldMouseCursorPosition = image.Point{mx, my}
	if cursorMoved {
		cursorPosition = oldMouseCursorPosition
	} else {
		const cursorInterval = 6
		switch {
		case keyStates[ebiten.KeyUp]%cursorInterval == 0:
			cursorPosition = cursorPosition.Add(image.Point{0, -1})
			cursorMoved = true
		case keyStates[ebiten.KeyDown]%cursorInterval == 0:
			cursorPosition = cursorPosition.Add(image.Point{0, +1})
			cursorMoved = true
		case keyStates[ebiten.KeyLeft]%cursorInterval == 0:
			cursorPosition = cursorPosition.Add(image.Point{-1, 0})
			cursorMoved = true
		case keyStates[ebiten.KeyRight]%cursorInterval == 0:
			cursorPosition = cursorPosition.Add(image.Point{+1, 0})
			cursorMoved = true
		}
	}
	if cursorBlinking == 127 {
		cursorBlinking = 0
	} else {
		cursorBlinking++
	}
	op := &ebiten.DrawImageOptions{}
	op.GeoM.Scale(0.25, .25)
	op.GeoM.Translate(float64(cursorPosition.X), float64(cursorPosition.Y))
	if cursorBlinking > 64 {
		op.ColorM.Scale(1, 1, 1, 0.25+float64(127-cursorBlinking)/255.0)
	} else {
		op.ColorM.Scale(1, 1, 1, 0.25+float64(cursorBlinking)/255.0)
	}
	if err := screen.DrawImage(cursorImage, op); err != nil {
		return err
	}
	if keyStates[ebiten.KeySpace] >= 0 ||
		ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
		if cursorMoved || !wasMouseButtonPressed {
			if err := togglePixel(cursorPosition); err != nil {
				return err
			}
			wasMouseButtonPressed = true
		}
	} else {
		wasMouseButtonPressed = false
	}
	return nil
}

func update(screen *ebiten.Image) error {
	readKeys()
	newSimulation := currentSimulation.Step()
	wires := currentSimulation.Circuit().Wires()
	for i, wire := range wires {
		oldCharge := currentSimulation.State(wire).Charge()
		charge := newSimulation.State(wire).Charge()
		if oldCharge == charge {
			continue
		}
		position := wire.Bounds().Min
		op := &ebiten.DrawImageOptions{}
		op.GeoM.Translate(float64(position.X), float64(position.Y))
		r, g, b, a := simulationImage.Palette[charge+1].RGBA()
		op.ColorM.Scale(float64(r)/0xFFFF, float64(g)/0xFFFF, float64(b)/0xFFFF, float64(a)/0xFFFF)
		if err := backgroundImage.DrawImage(wireImages[i], op); err != nil {
			return err
		}
	}
	currentSimulation = newSimulation
	if err := screen.DrawImage(backgroundImage, &ebiten.DrawImageOptions{}); err != nil {
		return err
	}

	if err := handleCursor(screen); err != nil {
		return err
	}
	return nil
}

func drawMask(wire *simulation.Wire) image.Image {
	bounds := image.Rect(0, 0, wire.Bounds().Dx(), wire.Bounds().Dy())
	bounds = bounds.Union(image.Rect(0, 0, 4, 4))
	position := wire.Bounds().Min
	img := image.NewRGBA(bounds)
	white := color.RGBA{0xFF, 0xFF, 0xFF, 0xFF}
	for _, pixel := range wire.Pixels() {
		img.SetRGBA(pixel.X-position.X, pixel.Y-position.Y, white)
	}
	return img
}
<|endoftext|>


<commit_before>package broadcaster_test

import (
	"github.com/aybabtme/broadcaster"
	"log"
	"math"
	"sync"
	"testing"
)

func TestCanBroadcast(t *testing.T) {
	b := broadcaster.New()

	wg := sync.WaitGroup{}

	l1 := b.Listen()

	wg.Add(3)
	go startListener(t, l1, "l1", &wg, 2)

	b.Send(1)

	l2 := b.Listen()
	go startListener(t, l2, "l2", &wg, 2)

	b.Send(2)

	l3 := b.Listen()
	go startListener(t, l3, "l3", &wg, 2)

	b.Send(3)

	b.Close()

	wg.Wait()
}

func TestCanBroadcastWithHistory(t *testing.T) {
	b := broadcaster.NewBacklog(4)

	b.Send(-4)
	b.Send(-3)
	b.Send(-2)
	b.Send(-1)

	wg := sync.WaitGroup{}

	l1 := b.Listen()

	wg.Add(3)
	go startListener(t, l1, "l1", &wg, 2)

	b.Send(1)

	l2 := b.Listen()
	go startListener(t, l2, "l2", &wg, 6)

	b.Send(2)

	l3 := b.Listen()
	go startListener(t, l3, "l3", &wg, 2)

	b.Send(3)

	b.Close()

	wg.Wait()
}

func startListener(t *testing.T, l broadcaster.Listener, name string, wg *sync.WaitGroup, times int) {
	defer wg.Done()
	defer l.Close()

	before := int(math.MinInt64)

	for i := 0; i < times; i++ {
		n, ok := l.Next()
		if !ok {
			log.Printf("%s: broadcaster closed!", name)
			return
		}

		now := n.(int)
		if now <= before {
			t.Errorf("%s received %d after %d", name, now, before)
		}
		before = now

		log.Printf("%s: msg %d/%d: got %d", name, i+1, times, n)
	}
	log.Printf("%s: done!", name)
}
<commit_msg>test within package<commit_after>package broadcaster

import (
	"log"
	"math"
	"sync"
	"testing"
)

func TestCanBroadcast(t *testing.T) {
	b := New()

	wg := sync.WaitGroup{}

	l1 := b.Listen()

	wg.Add(3)
	go startListener(t, l1, "l1", &wg, 2)

	b.Send(1)

	l2 := b.Listen()
	go startListener(t, l2, "l2", &wg, 2)

	b.Send(2)

	l3 := b.Listen()
	go startListener(t, l3, "l3", &wg, 2)

	b.Send(3)

	b.Close()

	wg.Wait()
}

func TestCanBroadcastWithHistory(t *testing.T) {
	b := NewBacklog(4)

	b.Send(-4)
	b.Send(-3)
	b.Send(-2)
	b.Send(-1)

	wg := sync.WaitGroup{}

	l1 := b.Listen()

	wg.Add(3)
	go startListener(t, l1, "l1", &wg, 2)

	b.Send(1)

	l2 := b.Listen()
	go startListener(t, l2, "l2", &wg, 6)

	b.Send(2)

	l3 := b.Listen()
	go startListener(t, l3, "l3", &wg, 2)

	b.Send(3)

	b.Close()

	wg.Wait()
}

func startListener(t *testing.T, l Listener, name string, wg *sync.WaitGroup, times int) {
	defer wg.Done()
	defer l.Close()

	before := int(math.MinInt64)

	for i := 0; i < times; i++ {
		n, ok := l.Next()
		if !ok {
			log.Printf("%s: broadcaster closed!", name)
			return
		}

		now := n.(int)
		if now <= before {
			t.Errorf("%s received %d after %d", name, now, before)
		}
		before = now

		log.Printf("%s: msg %d/%d: got %d", name, i+1, times, n)
	}
	log.Printf("%s: done!", name)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"

	"github.com/Bredgren/gogame"
)

func testRect() {
	var results []*result

	results = append(results, testRectInflate())
	results = append(results, testRectInflateIP())
	results = append(results, testRectClamp())
	results = append(results, testRectClampIP())
	results = append(results, testRectIntersect())
	results = append(results, testRectUnion())
	// results = append(results, testRectUnionIP())
	// results = append(results, testRectUnionAll())
	// results = append(results, testRectFit())
	// results = append(results, testRectNormalize())
	// results = append(results, testRectContains())
	// results = append(results, testRectCollidePoint())
	// results = append(results, testRectCollideRect())
	// results = append(results, testRectCollideList())
	// results = append(results, testRectCollideListAll())

	appendResultSection("Rect", results)
}

func testRectInflate() *result {
	res := result{TestName: "Inflate", Errors: []string{}}

	rect := gogame.Rect{X: 1, Y: 1, W: 5, H: 5}
	rect2 := rect.Inflate(2, 2)
	want := gogame.Rect{X: 0, Y: 0, W: 7, H: 7}
	if rect2 != want {
		res.Errors = append(res.Errors, fmt.Sprintf("got: %#v, want: %#v", rect2, want))
	}

	return &res
}

func testRectInflateIP() *result {
	res := result{TestName: "InflateIP", Errors: []string{}}

	rect := gogame.Rect{X: 1, Y: 1, W: 5, H: 5}
	rect.InflateIP(2, 2)
	want := gogame.Rect{X: 0, Y: 0, W: 7, H: 7}
	if rect != want {
		res.Errors = append(res.Errors, fmt.Sprintf("got: %#v, want: %#v", rect, want))
	}

	return &res
}

func testRectClamp() *result {
	res := result{TestName: "Clamp", Errors: []string{}}

	rect := gogame.Rect{X: 1, Y: 1, W: 5, H: 5}

	rect2 := gogame.Rect{X: 0, Y: 0, W: 1, H: 1}
	want := gogame.Rect{X: 1, Y: 1, W: 1, H: 1}
	got := rect2.Clamp(&rect)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Top left: got: %#v, want: %#v", got, want))
	}

	rect2 = gogame.Rect{X: 7, Y: 6, W: 1, H: 1}
	want = gogame.Rect{X: 5, Y: 5, W: 1, H: 1}
	got = rect2.Clamp(&rect)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Bottom right: got: %#v, want: %#v", got, want))
	}

	rect2 = gogame.Rect{X: 7, Y: 6, W: 7, H: 7}
	want = gogame.Rect{X: 0, Y: 0, W: 7, H: 7}
	got = rect2.Clamp(&rect)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Too big: got: %#v, want: %#v", got, want))
	}

	return &res
}

func testRectClampIP() *result {
	res := result{TestName: "ClampIP", Errors: []string{}}

	rect := gogame.Rect{X: 1, Y: 1, W: 5, H: 5}

	got := gogame.Rect{X: 0, Y: 0, W: 1, H: 1}
	want := gogame.Rect{X: 1, Y: 1, W: 1, H: 1}
	got.ClampIP(&rect)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Top left: got: %#v, want: %#v", got, want))
	}

	got = gogame.Rect{X: 7, Y: 6, W: 1, H: 1}
	want = gogame.Rect{X: 5, Y: 5, W: 1, H: 1}
	got.ClampIP(&rect)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Bottom right: got: %#v, want: %#v", got, want))
	}

	got = gogame.Rect{X: 7, Y: 6, W: 7, H: 7}
	want = gogame.Rect{X: 0, Y: 0, W: 7, H: 7}
	got.ClampIP(&rect)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Too big: got: %#v, want: %#v", got, want))
	}

	return &res
}

func testRectIntersect() *result {
	res := result{TestName: "Intersect", Errors: []string{}}

	rect := gogame.Rect{X: 1, Y: 1, W: 5, H: 5}

	rect2 := gogame.Rect{X: 0, Y: 0, W: 2, H: 3}
	want := gogame.Rect{X: 1, Y: 1, W: 1, H: 2}
	got := rect.Intersect(&rect2)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Top left: got: %#v, want: %#v", got, want))
	}

	rect2 = gogame.Rect{X: 2, Y: 3, W: 4, H: 5}
	want = gogame.Rect{X: 2, Y: 3, W: 4, H: 3}
	got = rect.Intersect(&rect2)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Bottom right: got: %#v, want: %#v", got, want))
	}

	rect2 = gogame.Rect{X: 2, Y: 2, W: 2, H: 2}
	want = gogame.Rect{X: 2, Y: 2, W: 2, H: 2}
	got = rect.Intersect(&rect2)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Inside: got: %#v, want: %#v", got, want))
	}

	rect2 = gogame.Rect{X: 6, Y: 6, W: 2, H: 2}
	want = gogame.Rect{X: 2, Y: 2, W: 0, H: 0}
	got = rect.Intersect(&rect2)
	if got.W != 0 && got.H != 0 {
		res.Errors = append(res.Errors, fmt.Sprintf("Outside: got: %#v, want: %#v", got, want))
	}

	return &res
}

func testRectUnion() *result {
	res := result{TestName: "Union", Errors: []string{}}

	rect := gogame.Rect{X: 1, Y: 1, W: 5, H: 5}

	rect2 := gogame.Rect{X: 0, Y: 0, W: 1, H: 1}
	want := gogame.Rect{X: 0, Y: 0, W: 6, H: 6}
	got := rect2.Union(&rect)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Top left: got: %#v, want: %#v", got, want))
	}

	rect2 = gogame.Rect{X: 4, Y: 3, W: 3, H: 3}
	want = gogame.Rect{X: 1, Y: 1, W: 6, H: 5}
	got = rect2.Union(&rect)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Bottom right: got: %#v, want: %#v", got, want))
	}

	rect2 = gogame.Rect{X: 2, Y: 2, W: 2, H: 2}
	want = gogame.Rect{X: 1, Y: 1, W: 5, H: 5}
	got = rect2.Union(&rect)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Inside: got: %#v, want: %#v", got, want))
	}

	rect2 = gogame.Rect{X: 7, Y: 6, W: 7, H: 7}
	want = gogame.Rect{X: 1, Y: 1, W: 13, H: 12}
	got = rect2.Union(&rect)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Outside: got: %#v, want: %#v", got, want))
	}

	return &res
}
<commit_msg>Add Rect tests for UnionIP and UnionAll. Start adding test for Fit.<commit_after>package main

import (
	"fmt"

	"github.com/Bredgren/gogame"
)

func testRect() {
	var results []*result

	results = append(results, testRectInflate())
	results = append(results, testRectInflateIP())
	results = append(results, testRectClamp())
	results = append(results, testRectClampIP())
	results = append(results, testRectIntersect())
	results = append(results, testRectUnion())
	results = append(results, testRectUnionIP())
	results = append(results, testRectUnionAll())
	results = append(results, testRectFit())
	// results = append(results, testRectNormalize())
	// results = append(results, testRectContains())
	// results = append(results, testRectCollidePoint())
	// results = append(results, testRectCollideRect())
	// results = append(results, testRectCollideList())
	// results = append(results, testRectCollideListAll())

	appendResultSection("Rect", results)
}

func testRectInflate() *result {
	res := result{TestName: "Inflate", Errors: []string{}}

	rect := gogame.Rect{X: 1, Y: 1, W: 5, H: 5}
	rect2 := rect.Inflate(2, 2)
	want := gogame.Rect{X: 0, Y: 0, W: 7, H: 7}
	if rect2 != want {
		res.Errors = append(res.Errors, fmt.Sprintf("got: %#v, want: %#v", rect2, want))
	}

	return &res
}

func testRectInflateIP() *result {
	res := result{TestName: "InflateIP", Errors: []string{}}

	rect := gogame.Rect{X: 1, Y: 1, W: 5, H: 5}
	rect.InflateIP(2, 2)
	want := gogame.Rect{X: 0, Y: 0, W: 7, H: 7}
	if rect != want {
		res.Errors = append(res.Errors, fmt.Sprintf("got: %#v, want: %#v", rect, want))
	}

	return &res
}

func testRectClamp() *result {
	res := result{TestName: "Clamp", Errors: []string{}}

	rect := gogame.Rect{X: 1, Y: 1, W: 5, H: 5}

	rect2 := gogame.Rect{X: 0, Y: 0, W: 1, H: 1}
	want := gogame.Rect{X: 1, Y: 1, W: 1, H: 1}
	got := rect2.Clamp(&rect)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Top left: got: %#v, want: %#v", got, want))
	}

	rect2 = gogame.Rect{X: 7, Y: 6, W: 1, H: 1}
	want = gogame.Rect{X: 5, Y: 5, W: 1, H: 1}
	got = rect2.Clamp(&rect)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Bottom right: got: %#v, want: %#v", got, want))
	}

	rect2 = gogame.Rect{X: 7, Y: 6, W: 7, H: 7}
	want = gogame.Rect{X: 0, Y: 0, W: 7, H: 7}
	got = rect2.Clamp(&rect)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Too big: got: %#v, want: %#v", got, want))
	}

	return &res
}

func testRectClampIP() *result {
	res := result{TestName: "ClampIP", Errors: []string{}}

	rect := gogame.Rect{X: 1, Y: 1, W: 5, H: 5}

	got := gogame.Rect{X: 0, Y: 0, W: 1, H: 1}
	want := gogame.Rect{X: 1, Y: 1, W: 1, H: 1}
	got.ClampIP(&rect)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Top left: got: %#v, want: %#v", got, want))
	}

	got = gogame.Rect{X: 7, Y: 6, W: 1, H: 1}
	want = gogame.Rect{X: 5, Y: 5, W: 1, H: 1}
	got.ClampIP(&rect)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Bottom right: got: %#v, want: %#v", got, want))
	}

	got = gogame.Rect{X: 7, Y: 6, W: 7, H: 7}
	want = gogame.Rect{X: 0, Y: 0, W: 7, H: 7}
	got.ClampIP(&rect)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Too big: got: %#v, want: %#v", got, want))
	}

	return &res
}

func testRectIntersect() *result {
	res := result{TestName: "Intersect", Errors: []string{}}

	rect := gogame.Rect{X: 1, Y: 1, W: 5, H: 5}

	rect2 := gogame.Rect{X: 0, Y: 0, W: 2, H: 3}
	want := gogame.Rect{X: 1, Y: 1, W: 1, H: 2}
	got := rect.Intersect(&rect2)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Top left: got: %#v, want: %#v", got, want))
	}

	rect2 = gogame.Rect{X: 2, Y: 3, W: 4, H: 5}
	want = gogame.Rect{X: 2, Y: 3, W: 4, H: 3}
	got = rect.Intersect(&rect2)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Bottom right: got: %#v, want: %#v", got, want))
	}

	rect2 = gogame.Rect{X: 2, Y: 2, W: 2, H: 2}
	want = gogame.Rect{X: 2, Y: 2, W: 2, H: 2}
	got = rect.Intersect(&rect2)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Inside: got: %#v, want: %#v", got, want))
	}

	rect2 = gogame.Rect{X: 6, Y: 6, W: 2, H: 2}
	want = gogame.Rect{X: 2, Y: 2, W: 0, H: 0}
	got = rect.Intersect(&rect2)
	if got.W != 0 && got.H != 0 {
		res.Errors = append(res.Errors, fmt.Sprintf("Outside: got: %#v, want: %#v", got, want))
	}

	return &res
}

func testRectUnion() *result {
	res := result{TestName: "Union", Errors: []string{}}

	rect := gogame.Rect{X: 1, Y: 1, W: 5, H: 5}

	rect2 := gogame.Rect{X: 0, Y: 0, W: 1, H: 1}
	want := gogame.Rect{X: 0, Y: 0, W: 6, H: 6}
	got := rect2.Union(&rect)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Top left: got: %#v, want: %#v", got, want))
	}

	rect2 = gogame.Rect{X: 4, Y: 3, W: 3, H: 3}
	want = gogame.Rect{X: 1, Y: 1, W: 6, H: 5}
	got = rect2.Union(&rect)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Bottom right: got: %#v, want: %#v", got, want))
	}

	rect2 = gogame.Rect{X: 2, Y: 2, W: 2, H: 2}
	want = gogame.Rect{X: 1, Y: 1, W: 5, H: 5}
	got = rect2.Union(&rect)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Inside: got: %#v, want: %#v", got, want))
	}

	rect2 = gogame.Rect{X: 7, Y: 6, W: 7, H: 7}
	want = gogame.Rect{X: 1, Y: 1, W: 13, H: 12}
	got = rect2.Union(&rect)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Outside: got: %#v, want: %#v", got, want))
	}

	return &res
}

func testRectUnionIP() *result {
	res := result{TestName: "UnionIP", Errors: []string{}}

	rect := gogame.Rect{X: 1, Y: 1, W: 5, H: 5}

	got := gogame.Rect{X: 0, Y: 0, W: 1, H: 1}
	want := gogame.Rect{X: 0, Y: 0, W: 6, H: 6}
	got.UnionIP(&rect)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Top left: got: %#v, want: %#v", got, want))
	}

	got = gogame.Rect{X: 4, Y: 3, W: 3, H: 3}
	want = gogame.Rect{X: 1, Y: 1, W: 6, H: 5}
	got.UnionIP(&rect)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Bottom right: got: %#v, want: %#v", got, want))
	}

	got = gogame.Rect{X: 2, Y: 2, W: 2, H: 2}
	want = gogame.Rect{X: 1, Y: 1, W: 5, H: 5}
	got.UnionIP(&rect)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Inside: got: %#v, want: %#v", got, want))
	}

	got = gogame.Rect{X: 7, Y: 6, W: 7, H: 7}
	want = gogame.Rect{X: 1, Y: 1, W: 13, H: 12}
	got.UnionIP(&rect)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("Outside: got: %#v, want: %#v", got, want))
	}

	return &res
}

func testRectUnionAll() *result {
	res := result{TestName: "UnionAll", Errors: []string{}}

	rect := gogame.Rect{X: 1, Y: 1, W: 5, H: 5}

	rects := []*gogame.Rect{
		&gogame.Rect{X: 0, Y: 2, W: 3, H: 6},
		&gogame.Rect{X: 4, Y: -1, W: 4, H: 4},
	}

	want := gogame.Rect{X: 0, Y: -1, W: 8, H: 9}
	got := rect.UnionAll(rects)
	if got != want {
		res.Errors = append(res.Errors, fmt.Sprintf("got: %#v, want: %#v", got, want))
	}

	return &res
}

func testRectFit() *result {
	res := result{TestName: "Fit", Errors: []string{}}

	rect1 := gogame.Rect{X: 1, Y: 1, W: 6, H: 3}
	rect2 := gogame.Rect{X: 2, Y: 2, W: 3, H: 6}
	rect3 := gogame.Rect{X: 3, Y: 3, W: 8, H: 2}
	rect4 := gogame.Rect{X: 4, Y: 4, W: 2, H: 8}

	cases := []struct {
		r1, r2, want *gogame.Rect
	}{
		{&rect1, &rect2, &gogame.Rect{X: 2, Y: 2, W: 3, H: 1.5}},
		{&rect1, &rect3, &gogame.Rect{X: 3, Y: 3, W: 4, H: 2}},
		{&rect1, &rect4, &gogame.Rect{X: 4, Y: 4, W: 2, H: 1}},
		{&rect2, &rect1, &gogame.Rect{X: 1, Y: 1, W: 1.5, H: 3}},
		{&rect2, &rect3, &gogame.Rect{X: 3, Y: 3, W: 1, H: 2}},
		{&rect2, &rect4, &gogame.Rect{X: 4, Y: 4, W: 2, H: 4}},
	}

	for i, c := range cases {
		got := c.r1.Fit(c.r2)
		if got != *c.want {
			res.Errors = append(res.Errors, fmt.Sprintf("%d: got: %#v, want: %#v", i, got, c.want))
		}
	}

	return &res
}
<|endoftext|>


<commit_before>// +build ignore

package main

import (
	"flag"
	"os"
	"os/signal"
	"time"

	"github.com/kidoman/embd"
	"github.com/kidoman/embd/motion/servo"
)

func main() {
	flag.Parse()

	embd.InitGPIO()
	defer embd.CloseGPIO()

	pwm, err := embd.NewPWMPin("P9_14")
	if err != nil {
		panic(err)
	}
	defer pwm.Close()

	servo := servo.New(pwm)

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, os.Kill)

	turnTimer := time.Tick(500 * time.Millisecond)
	left := true

	servo.SetAngle(90)
	defer func() {
		servo.SetAngle(90)
	}()

	for {
		select {
		case <-turnTimer:
			left = !left
			switch left {
			case true:
				servo.SetAngle(70)
			case false:
				servo.SetAngle(110)
			}
		case <-c:
			return
		}
	}
}
<commit_msg>samples: servobbb cleanup<commit_after>// +build ignore

package main

import (
	"flag"
	"os"
	"os/signal"
	"time"

	"github.com/kidoman/embd"
	"github.com/kidoman/embd/motion/servo"
)

func main() {
	flag.Parse()

	embd.InitGPIO()
	defer embd.CloseGPIO()

	pwm, err := embd.NewPWMPin("P9_14")
	if err != nil {
		panic(err)
	}
	defer pwm.Close()

	servo := servo.New(pwm)

	quit := make(chan os.Signal, 1)
	signal.Notify(quit, os.Interrupt, os.Kill)

	turnTimer := time.Tick(500 * time.Millisecond)
	left := true

	servo.SetAngle(90)
	defer func() {
		servo.SetAngle(90)
	}()

	for {
		select {
		case <-turnTimer:
			left = !left
			switch left {
			case true:
				servo.SetAngle(70)
			case false:
				servo.SetAngle(110)
			}
		case <-quit:
			return
		}
	}
}
<|endoftext|>


<commit_before>package cmd

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

	"github.com/gobuffalo/buffalo/meta"
	"github.com/gobuffalo/envy"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"

	"github.com/gobuffalo/pop"
	"github.com/spf13/cobra"
)

const vendorPattern = "/vendor/"

var vendorRegex = regexp.MustCompile(vendorPattern)
var forceMigrations = false

func init() {
	decorate("test", testCmd)
	testCmd.Flags().BoolVarP(&forceMigrations, "force-migrations", "m", false, "skips loading the schema and instead runs migrations before running tests")

	RootCmd.AddCommand(testCmd)
}

var testCmd = &cobra.Command{
	Use:                "test",
	Short:              "Runs the tests for your Buffalo app",
	DisableFlagParsing: true,
	RunE: func(c *cobra.Command, args []string) error {
		os.Setenv("GO_ENV", "test")
		if _, err := os.Stat("database.yml"); err == nil {
			// there's a database
			test, err := pop.Connect("test")
			if err != nil {
				return errors.WithStack(err)
			}

			// drop the test db:
			test.Dialect.DropDB()

			// create the test db:
			err = test.Dialect.CreateDB()
			if err != nil {
				return errors.WithStack(err)
			}

			if forceMigrations {
				fm, err := pop.NewFileMigrator("./migrations", test)

				if err != nil {
					return err
				}

				if err := fm.Up(); err != nil {
					return err
				}

				return testRunner(args)
			}

			if schema := findSchema(); schema != nil {
				err = test.Dialect.LoadSchema(schema)
				if err != nil {
					return errors.WithStack(err)
				}
			}
		}
		return testRunner(args)
	},
}

func findSchema() io.Reader {
	if f, err := os.Open(filepath.Join("migrations", "schema.sql")); err == nil {
		return f
	}
	if dev, err := pop.Connect("development"); err == nil {
		schema := &bytes.Buffer{}
		if err = dev.Dialect.DumpSchema(schema); err == nil {
			return schema
		}
	}

	if test, err := pop.Connect("test"); err == nil {
		fm, err := pop.NewFileMigrator("./migrations", test)
		if err != nil {
			return nil
		}

		if err := fm.Up(); err == nil {
			if f, err := os.Open(filepath.Join("migrations", "schema.sql")); err == nil {
				return f
			}
		}
	}
	return nil
}

func testRunner(args []string) error {
	var mFlag bool
	var query string
	cargs := []string{}
	pargs := []string{}
	var larg string
	for i, a := range args {
		switch a {
		case "-run", "-m":
			query = args[i+1]
			mFlag = true
		case "-v":
			cargs = append(cargs, "-v")
		default:
			if larg != "-run" && larg != "-m" {
				pargs = append(pargs, a)
			}
		}
		larg = a
	}

	cmd := newTestCmd(cargs)
	if mFlag {
		return mFlagRunner{
			query: query,
			args:  cargs,
			pargs: pargs,
		}.Run()
	}

	pkgs, err := testPackages(pargs)
	if err != nil {
		return errors.WithStack(err)
	}
	cmd.Args = append(cmd.Args, pkgs...)
	logrus.Info(strings.Join(cmd.Args, " "))
	return cmd.Run()
}

type mFlagRunner struct {
	query string
	args  []string
	pargs []string
}

func (m mFlagRunner) Run() error {
	app := meta.New(".")
	pwd, _ := os.Getwd()
	defer os.Chdir(pwd)

	pkgs, err := testPackages(m.pargs)
	if err != nil {
		return errors.WithStack(err)
	}
	var errs bool
	for _, p := range pkgs {
		os.Chdir(pwd)
		if p == app.PackagePkg {
			continue
		}
		p = strings.TrimPrefix(p, app.PackagePkg+string(filepath.Separator))
		os.Chdir(p)

		cmd := newTestCmd(m.args)
		if hasTestify(p) {
			cmd.Args = append(cmd.Args, "-testify.m", m.query)
		} else {
			cmd.Args = append(cmd.Args, "-run", m.query)
		}
		logrus.Info(strings.Join(cmd.Args, " "))
		if err := cmd.Run(); err != nil {
			errs = true
		}
	}
	if errs {
		return errors.New("errors running tests")
	}
	return nil
}

func hasTestify(p string) bool {
	cmd := exec.Command("go", "test", "-thisflagdoesntexist")
	b, _ := cmd.Output()
	return bytes.Contains(b, []byte("-testify.m"))
}

func testPackages(givenArgs []string) ([]string, error) {
	// If there are args, then assume these are the packages to test.
	//
	// Instead of always returning all packages from 'go list ./...', just
	// return the given packages in this case
	if len(givenArgs) > 0 {
		return givenArgs, nil
	}
	args := []string{}
	out, err := exec.Command(envy.Get("GO_BIN", "go"), "list", "./...").Output()
	if err != nil {
		return args, err
	}
	pkgs := bytes.Split(bytes.TrimSpace(out), []byte("\n"))
	for _, p := range pkgs {
		if !vendorRegex.Match(p) {
			args = append(args, string(p))
		}
	}
	return args, nil
}

func newTestCmd(args []string) *exec.Cmd {
	cargs := []string{"test", "-p", "1"}
	app := meta.New(".")
	tf := "-tags " + app.BuildTags("development").String()
	cargs = append(cargs, tf)
	cargs = append(cargs, args...)
	cmd := exec.Command(envy.Get("GO_BIN", "go"), cargs...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd
}
<commit_msg>Attempting to fix test runner (#1133)<commit_after>package cmd

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

	"github.com/gobuffalo/buffalo/meta"
	"github.com/gobuffalo/envy"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"

	"github.com/gobuffalo/pop"
	"github.com/spf13/cobra"
)

const vendorPattern = "/vendor/"

var vendorRegex = regexp.MustCompile(vendorPattern)
var forceMigrations = false

func init() {
	decorate("test", testCmd)
	testCmd.Flags().BoolVarP(&forceMigrations, "force-migrations", "m", false, "skips loading the schema and instead runs migrations before running tests")

	RootCmd.AddCommand(testCmd)
}

var testCmd = &cobra.Command{
	Use:                "test",
	Short:              "Runs the tests for your Buffalo app",
	DisableFlagParsing: true,
	RunE: func(c *cobra.Command, args []string) error {
		os.Setenv("GO_ENV", "test")
		if _, err := os.Stat("database.yml"); err == nil {
			// there's a database
			test, err := pop.Connect("test")
			if err != nil {
				return errors.WithStack(err)
			}

			// drop the test db:
			test.Dialect.DropDB()

			// create the test db:
			err = test.Dialect.CreateDB()
			if err != nil {
				return errors.WithStack(err)
			}

			if forceMigrations {
				fm, err := pop.NewFileMigrator("./migrations", test)

				if err != nil {
					return err
				}

				if err := fm.Up(); err != nil {
					return err
				}

				return testRunner(args)
			}

			if schema := findSchema(); schema != nil {
				err = test.Dialect.LoadSchema(schema)
				if err != nil {
					return errors.WithStack(err)
				}
			}
		}
		return testRunner(args)
	},
}

func findSchema() io.Reader {
	if f, err := os.Open(filepath.Join("migrations", "schema.sql")); err == nil {
		return f
	}
	if dev, err := pop.Connect("development"); err == nil {
		schema := &bytes.Buffer{}
		if err = dev.Dialect.DumpSchema(schema); err == nil {
			return schema
		}
	}

	if test, err := pop.Connect("test"); err == nil {
		fm, err := pop.NewFileMigrator("./migrations", test)
		if err != nil {
			return nil
		}

		if err := fm.Up(); err == nil {
			if f, err := os.Open(filepath.Join("migrations", "schema.sql")); err == nil {
				return f
			}
		}
	}
	return nil
}

func testRunner(args []string) error {
	var mFlag bool
	var query string
	cargs := []string{}
	pargs := []string{}
	var larg string
	for i, a := range args {
		switch a {
		case "-run", "-m":
			query = args[i+1]
			mFlag = true
		case "-v":
			cargs = append(cargs, "-v")
		default:
			if larg != "-run" && larg != "-m" {
				pargs = append(pargs, a)
			}
		}
		larg = a
	}

	cmd := newTestCmd(cargs)
	if mFlag {
		return mFlagRunner{
			query: query,
			args:  cargs,
			pargs: pargs,
		}.Run()
	}

	pkgs, err := testPackages(pargs)
	if err != nil {
		return errors.WithStack(err)
	}
	cmd.Args = append(cmd.Args, pkgs...)
	logrus.Info(strings.Join(cmd.Args, " "))
	return cmd.Run()
}

type mFlagRunner struct {
	query string
	args  []string
	pargs []string
}

func (m mFlagRunner) Run() error {
	app := meta.New(".")
	pwd, _ := os.Getwd()
	defer os.Chdir(pwd)

	pkgs, err := testPackages(m.pargs)
	if err != nil {
		return errors.WithStack(err)
	}
	var errs bool
	for _, p := range pkgs {
		os.Chdir(pwd)
		if p == app.PackagePkg {
			continue
		}
		p = strings.TrimPrefix(p, app.PackagePkg+string(filepath.Separator))
		os.Chdir(p)

		cmd := newTestCmd(m.args)
		if hasTestify(p) {
			cmd.Args = append(cmd.Args, "-testify.m", m.query)
		} else {
			cmd.Args = append(cmd.Args, "-run", m.query)
		}
		logrus.Info(strings.Join(cmd.Args, " "))
		if err := cmd.Run(); err != nil {
			errs = true
		}
	}
	if errs {
		return errors.New("errors running tests")
	}
	return nil
}

func hasTestify(p string) bool {
	cmd := exec.Command("go", "test", "-thisflagdoesntexist")
	b, _ := cmd.Output()
	return bytes.Contains(b, []byte("-testify.m"))
}

func testPackages(givenArgs []string) ([]string, error) {
	// If there are args, then assume these are the packages to test.
	//
	// Instead of always returning all packages from 'go list ./...', just
	// return the given packages in this case
	if len(givenArgs) > 0 {
		return givenArgs, nil
	}
	args := []string{}
	out, err := exec.Command(envy.Get("GO_BIN", "go"), "list", "./...").Output()
	if err != nil {
		return args, err
	}
	pkgs := bytes.Split(bytes.TrimSpace(out), []byte("\n"))
	for _, p := range pkgs {
		if !vendorRegex.Match(p) {
			args = append(args, string(p))
		}
	}
	return args, nil
}

func newTestCmd(args []string) *exec.Cmd {
	cargs := []string{"test", "-p", "1"}
	app := meta.New(".")
	cargs = append(cargs, "-tags", app.BuildTags("development").String())
	cargs = append(cargs, args...)
	cmd := exec.Command(envy.Get("GO_BIN", "go"), cargs...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd
}
<|endoftext|>