<commit_before>// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package interfacer

import (
	"fmt"
	"go/ast"
	"go/token"
	"go/types"
	"os"
	"strings"

	"golang.org/x/tools/go/loader"
	"golang.org/x/tools/go/ssa"
	"golang.org/x/tools/go/ssa/ssautil"

	"github.com/kisielk/gotool"
	"github.com/mvdan/lint"
)

func toDiscard(usage *varUsage) bool {
	if usage.discard {
		return true
	}
	for to := range usage.assigned {
		if toDiscard(to) {
			return true
		}
	}
	return false
}

func allCalls(usage *varUsage, all, ftypes map[string]string) {
	for fname := range usage.calls {
		all[fname] = ftypes[fname]
	}
	for to := range usage.assigned {
		allCalls(to, all, ftypes)
	}
}

func (c *Checker) interfaceMatching(param *types.Var, usage *varUsage) (string, string) {
	if toDiscard(usage) {
		return "", ""
	}
	ftypes := typeFuncMap(param.Type())
	called := make(map[string]string, len(usage.calls))
	allCalls(usage, called, ftypes)
	s := funcMapString(called)
	return c.ifaces[s], s
}

type varUsage struct {
	calls   map[string]struct{}
	discard bool

	assigned map[*varUsage]struct{}
}

type funcDecl struct {
	astDecl *ast.FuncDecl
	ssaFn   *ssa.Function
}

// CheckArgs checks the packages specified by their import paths in
// args.
func CheckArgs(args []string) ([]string, error) {
	paths := gotool.ImportPaths(args)
	conf := loader.Config{}
	conf.AllowErrors = true
	rest, err := conf.FromArgs(paths, false)
	if err != nil {
		return nil, err
	}
	if len(rest) > 0 {
		return nil, fmt.Errorf("unwanted extra args: %v", rest)
	}
	lprog, err := conf.Load()
	if err != nil {
		return nil, err
	}
	prog := ssautil.CreateProgram(lprog, 0)
	prog.Build()
	c := new(Checker)
	c.Program(lprog)
	c.ProgramSSA(prog)
	issues, err := c.Check()
	if err != nil {
		return nil, err
	}
	wd, err := os.Getwd()
	if err != nil {
		return nil, err
	}
	lines := make([]string, len(issues))
	for i, issue := range issues {
		fpos := prog.Fset.Position(issue.Pos()).String()
		if strings.HasPrefix(fpos, wd) {
			fpos = fpos[len(wd)+1:]
		}
		lines[i] = fmt.Sprintf("%s: %s", fpos, issue.Message())
	}
	return lines, nil
}

type Checker struct {
	lprog *loader.Program
	prog  *ssa.Program

	pkgTypes
	*loader.PackageInfo

	fset  *token.FileSet
	funcs []*funcDecl

	ssaByPos map[token.Pos]*ssa.Function

	discardFuncs map[*types.Signature]struct{}

	vars map[*types.Var]*varUsage
}

func (c *Checker) Program(lprog *loader.Program) {
	c.lprog = lprog
}

func (c *Checker) ProgramSSA(prog *ssa.Program) {
	c.prog = prog
}

func (c *Checker) Check() ([]lint.Issue, error) {
	var total []lint.Issue
	c.ssaByPos = make(map[token.Pos]*ssa.Function)
	wantPkg := make(map[*types.Package]bool)
	for _, pinfo := range c.lprog.InitialPackages() {
		wantPkg[pinfo.Pkg] = true
	}
	for fn := range ssautil.AllFunctions(c.prog) {
		if fn.Pkg == nil { // builtin?
			continue
		}
		if len(fn.Blocks) == 0 { // stub
			continue
		}
		if !wantPkg[fn.Pkg.Pkg] { // not part of given pkgs
			continue
		}
		c.ssaByPos[fn.Pos()] = fn
	}
	for _, pinfo := range c.lprog.InitialPackages() {
		pkg := pinfo.Pkg
		c.getTypes(pkg)
		total = append(total, c.checkPkg(c.lprog.AllPackages[pkg])...)
	}
	return total, nil
}

func (c *Checker) checkPkg(info *loader.PackageInfo) []lint.Issue {
	c.PackageInfo = info
	c.discardFuncs = make(map[*types.Signature]struct{})
	c.vars = make(map[*types.Var]*varUsage)
	for _, f := range info.Files {
		ast.Inspect(f, func(node ast.Node) bool {
			decl, ok := node.(*ast.FuncDecl)
			if !ok {
				return true
			}
			ssaFn := c.ssaByPos[decl.Name.Pos()]
			if ssaFn == nil {
				return true
			}
			fd := &funcDecl{
				astDecl: decl,
				ssaFn:   ssaFn,
			}
			if c.funcSigns[signString(fd.ssaFn.Signature)] {
				// implements interface
				return true
			}
			c.funcs = append(c.funcs, fd)
			return true
		})
		ast.Walk(c, f)
	}
	return c.packageIssues()
}

func paramVarAndType(sign *types.Signature, i int) (*types.Var, types.Type) {
	params := sign.Params()
	extra := sign.Variadic() && i >= params.Len()-1
	if !extra {
		if i >= params.Len() {
			// builtins with multiple signatures
			return nil, nil
		}
		vr := params.At(i)
		return vr, vr.Type()
	}
	last := params.At(params.Len() - 1)
	switch x := last.Type().(type) {
	case *types.Slice:
		return nil, x.Elem()
	default:
		return nil, x
	}
}

func (c *Checker) varUsage(e ast.Expr) *varUsage {
	id, ok := e.(*ast.Ident)
	if !ok {
		return nil
	}
	param, ok := c.ObjectOf(id).(*types.Var)
	if !ok {
		// not a variable
		return nil
	}
	if usage, e := c.vars[param]; e {
		return usage
	}
	if !interesting(param.Type()) {
		return nil
	}
	usage := &varUsage{
		calls:    make(map[string]struct{}),
		assigned: make(map[*varUsage]struct{}),
	}
	c.vars[param] = usage
	return usage
}

func (c *Checker) addUsed(e ast.Expr, as types.Type) {
	if as == nil {
		return
	}
	if usage := c.varUsage(e); usage != nil {
		// using variable
		iface, ok := as.Underlying().(*types.Interface)
		if !ok {
			usage.discard = true
			return
		}
		for i := 0; i < iface.NumMethods(); i++ {
			m := iface.Method(i)
			usage.calls[m.Name()] = struct{}{}
		}
	} else if t, ok := c.TypeOf(e).(*types.Signature); ok {
		// using func
		c.discardFuncs[t] = struct{}{}
	}
}

func (c *Checker) addAssign(to, from ast.Expr) {
	pto := c.varUsage(to)
	pfrom := c.varUsage(from)
	if pto == nil || pfrom == nil {
		// either isn't interesting
		return
	}
	pfrom.assigned[pto] = struct{}{}
}

func (c *Checker) discard(e ast.Expr) {
	if usage := c.varUsage(e); usage != nil {
		usage.discard = true
	}
}

func (c *Checker) comparedWith(e, with ast.Expr) {
	if _, ok := with.(*ast.BasicLit); ok {
		c.discard(e)
	}
}

func (c *Checker) Visit(node ast.Node) ast.Visitor {
	switch x := node.(type) {
	case *ast.SelectorExpr:
		if _, ok := c.TypeOf(x.Sel).(*types.Signature); !ok {
			c.discard(x.X)
		}
	case *ast.StarExpr:
		c.discard(x.X)
	case *ast.UnaryExpr:
		c.discard(x.X)
	case *ast.IndexExpr:
		c.discard(x.X)
	case *ast.IncDecStmt:
		c.discard(x.X)
	case *ast.BinaryExpr:
		switch x.Op {
		case token.EQL, token.NEQ:
			c.comparedWith(x.X, x.Y)
			c.comparedWith(x.Y, x.X)
		default:
			c.discard(x.X)
			c.discard(x.Y)
		}
	case *ast.ValueSpec:
		for _, val := range x.Values {
			c.addUsed(val, c.TypeOf(x.Type))
		}
	case *ast.AssignStmt:
		for i, val := range x.Rhs {
			left := x.Lhs[i]
			if x.Tok == token.ASSIGN {
				c.addUsed(val, c.TypeOf(left))
			}
			c.addAssign(left, val)
		}
	case *ast.CompositeLit:
		for i, e := range x.Elts {
			switch y := e.(type) {
			case *ast.KeyValueExpr:
				c.addUsed(y.Key, c.TypeOf(y.Value))
				c.addUsed(y.Value, c.TypeOf(y.Key))
			case *ast.Ident:
				c.addUsed(y, compositeIdentType(c.TypeOf(x), i))
			}
		}
	case *ast.CallExpr:
		switch y := c.TypeOf(x.Fun).Underlying().(type) {
		case *types.Signature:
			c.onMethodCall(x, y)
		default:
			// type conversion
			if len(x.Args) == 1 {
				c.addUsed(x.Args[0], y)
			}
		}
	}
	return c
}

func compositeIdentType(t types.Type, i int) types.Type {
	switch x := t.(type) {
	case *types.Named:
		return compositeIdentType(x.Underlying(), i)
	case *types.Struct:
		return x.Field(i).Type()
	case *types.Array:
		return x.Elem()
	case *types.Slice:
		return x.Elem()
	}
	return nil
}

func (c *Checker) onMethodCall(ce *ast.CallExpr, sign *types.Signature) {
	for i, e := range ce.Args {
		paramObj, t := paramVarAndType(sign, i)
		// Don't if this is a parameter being re-used as itself
		// in a recursive call
		if id, ok := e.(*ast.Ident); ok {
			if paramObj == c.ObjectOf(id) {
				continue
			}
		}
		c.addUsed(e, t)
	}
	sel, ok := ce.Fun.(*ast.SelectorExpr)
	if !ok {
		return
	}
	// receiver func call on the left side
	if usage := c.varUsage(sel.X); usage != nil {
		usage.calls[sel.Sel.Name] = struct{}{}
	}
}

func (fd *funcDecl) paramGroups() [][]*types.Var {
	astList := fd.astDecl.Type.Params.List
	groups := make([][]*types.Var, len(astList))
	signIndex := 0
	for i, field := range astList {
		group := make([]*types.Var, len(field.Names))
		for j := range field.Names {
			group[j] = fd.ssaFn.Signature.Params().At(signIndex)
			signIndex++
		}
		groups[i] = group
	}
	return groups
}

func (c *Checker) packageIssues() []lint.Issue {
	var issues []lint.Issue
	for _, fd := range c.funcs {
		if _, e := c.discardFuncs[fd.ssaFn.Signature]; e {
			continue
		}
		for _, group := range fd.paramGroups() {
			issues = append(issues, c.groupIssues(fd, group)...)
		}
	}
	return issues
}

type Issue struct {
	pos token.Pos
	msg string
}

func (i Issue) Pos() token.Pos  { return i.pos }
func (i Issue) Message() string { return i.msg }

func (c *Checker) groupIssues(fd *funcDecl, group []*types.Var) []lint.Issue {
	var issues []lint.Issue
	for _, param := range group {
		usage := c.vars[param]
		if usage == nil {
			return nil
		}
		newType := c.paramNewType(fd.astDecl.Name.Name, param, usage)
		if newType == "" {
			return nil
		}
		issues = append(issues, Issue{
			pos: param.Pos(),
			msg: fmt.Sprintf("%s can be %s", param.Name(), newType),
		})
	}
	return issues
}

func willAddAllocation(t types.Type) bool {
	switch t.Underlying().(type) {
	case *types.Pointer, *types.Interface:
		return false
	}
	return true
}

func (c *Checker) paramNewType(funcName string, param *types.Var, usage *varUsage) string {
	t := param.Type()
	if !ast.IsExported(funcName) && willAddAllocation(t) {
		return ""
	}
	if named := typeNamed(t); named != nil {
		tname := named.Obj().Name()
		vname := param.Name()
		if mentionsName(funcName, tname) || mentionsName(funcName, vname) {
			return ""
		}
	}
	ifname, iftype := c.interfaceMatching(param, usage)
	if ifname == "" {
		return ""
	}
	if types.IsInterface(t.Underlying()) {
		if have := funcMapString(typeFuncMap(t)); have == iftype {
			return ""
		}
	}
	return ifname
}
<commit_msg>Verify impelemtation of Checker, WithSSA<commit_after>// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package interfacer

import (
	"fmt"
	"go/ast"
	"go/token"
	"go/types"
	"os"
	"strings"

	"golang.org/x/tools/go/loader"
	"golang.org/x/tools/go/ssa"
	"golang.org/x/tools/go/ssa/ssautil"

	"github.com/kisielk/gotool"
	"github.com/mvdan/lint"
)

func toDiscard(usage *varUsage) bool {
	if usage.discard {
		return true
	}
	for to := range usage.assigned {
		if toDiscard(to) {
			return true
		}
	}
	return false
}

func allCalls(usage *varUsage, all, ftypes map[string]string) {
	for fname := range usage.calls {
		all[fname] = ftypes[fname]
	}
	for to := range usage.assigned {
		allCalls(to, all, ftypes)
	}
}

func (c *Checker) interfaceMatching(param *types.Var, usage *varUsage) (string, string) {
	if toDiscard(usage) {
		return "", ""
	}
	ftypes := typeFuncMap(param.Type())
	called := make(map[string]string, len(usage.calls))
	allCalls(usage, called, ftypes)
	s := funcMapString(called)
	return c.ifaces[s], s
}

type varUsage struct {
	calls   map[string]struct{}
	discard bool

	assigned map[*varUsage]struct{}
}

type funcDecl struct {
	astDecl *ast.FuncDecl
	ssaFn   *ssa.Function
}

// CheckArgs checks the packages specified by their import paths in
// args.
func CheckArgs(args []string) ([]string, error) {
	paths := gotool.ImportPaths(args)
	conf := loader.Config{}
	conf.AllowErrors = true
	rest, err := conf.FromArgs(paths, false)
	if err != nil {
		return nil, err
	}
	if len(rest) > 0 {
		return nil, fmt.Errorf("unwanted extra args: %v", rest)
	}
	lprog, err := conf.Load()
	if err != nil {
		return nil, err
	}
	prog := ssautil.CreateProgram(lprog, 0)
	prog.Build()
	c := new(Checker)
	c.Program(lprog)
	c.ProgramSSA(prog)
	issues, err := c.Check()
	if err != nil {
		return nil, err
	}
	wd, err := os.Getwd()
	if err != nil {
		return nil, err
	}
	lines := make([]string, len(issues))
	for i, issue := range issues {
		fpos := prog.Fset.Position(issue.Pos()).String()
		if strings.HasPrefix(fpos, wd) {
			fpos = fpos[len(wd)+1:]
		}
		lines[i] = fmt.Sprintf("%s: %s", fpos, issue.Message())
	}
	return lines, nil
}

type Checker struct {
	lprog *loader.Program
	prog  *ssa.Program

	pkgTypes
	*loader.PackageInfo

	fset  *token.FileSet
	funcs []*funcDecl

	ssaByPos map[token.Pos]*ssa.Function

	discardFuncs map[*types.Signature]struct{}

	vars map[*types.Var]*varUsage
}

var (
	_ lint.Checker = (*Checker)(nil)
	_ lint.WithSSA = (*Checker)(nil)
)

func (c *Checker) Program(lprog *loader.Program) {
	c.lprog = lprog
}

func (c *Checker) ProgramSSA(prog *ssa.Program) {
	c.prog = prog
}

func (c *Checker) Check() ([]lint.Issue, error) {
	var total []lint.Issue
	c.ssaByPos = make(map[token.Pos]*ssa.Function)
	wantPkg := make(map[*types.Package]bool)
	for _, pinfo := range c.lprog.InitialPackages() {
		wantPkg[pinfo.Pkg] = true
	}
	for fn := range ssautil.AllFunctions(c.prog) {
		if fn.Pkg == nil { // builtin?
			continue
		}
		if len(fn.Blocks) == 0 { // stub
			continue
		}
		if !wantPkg[fn.Pkg.Pkg] { // not part of given pkgs
			continue
		}
		c.ssaByPos[fn.Pos()] = fn
	}
	for _, pinfo := range c.lprog.InitialPackages() {
		pkg := pinfo.Pkg
		c.getTypes(pkg)
		total = append(total, c.checkPkg(c.lprog.AllPackages[pkg])...)
	}
	return total, nil
}

func (c *Checker) checkPkg(info *loader.PackageInfo) []lint.Issue {
	c.PackageInfo = info
	c.discardFuncs = make(map[*types.Signature]struct{})
	c.vars = make(map[*types.Var]*varUsage)
	for _, f := range info.Files {
		ast.Inspect(f, func(node ast.Node) bool {
			decl, ok := node.(*ast.FuncDecl)
			if !ok {
				return true
			}
			ssaFn := c.ssaByPos[decl.Name.Pos()]
			if ssaFn == nil {
				return true
			}
			fd := &funcDecl{
				astDecl: decl,
				ssaFn:   ssaFn,
			}
			if c.funcSigns[signString(fd.ssaFn.Signature)] {
				// implements interface
				return true
			}
			c.funcs = append(c.funcs, fd)
			return true
		})
		ast.Walk(c, f)
	}
	return c.packageIssues()
}

func paramVarAndType(sign *types.Signature, i int) (*types.Var, types.Type) {
	params := sign.Params()
	extra := sign.Variadic() && i >= params.Len()-1
	if !extra {
		if i >= params.Len() {
			// builtins with multiple signatures
			return nil, nil
		}
		vr := params.At(i)
		return vr, vr.Type()
	}
	last := params.At(params.Len() - 1)
	switch x := last.Type().(type) {
	case *types.Slice:
		return nil, x.Elem()
	default:
		return nil, x
	}
}

func (c *Checker) varUsage(e ast.Expr) *varUsage {
	id, ok := e.(*ast.Ident)
	if !ok {
		return nil
	}
	param, ok := c.ObjectOf(id).(*types.Var)
	if !ok {
		// not a variable
		return nil
	}
	if usage, e := c.vars[param]; e {
		return usage
	}
	if !interesting(param.Type()) {
		return nil
	}
	usage := &varUsage{
		calls:    make(map[string]struct{}),
		assigned: make(map[*varUsage]struct{}),
	}
	c.vars[param] = usage
	return usage
}

func (c *Checker) addUsed(e ast.Expr, as types.Type) {
	if as == nil {
		return
	}
	if usage := c.varUsage(e); usage != nil {
		// using variable
		iface, ok := as.Underlying().(*types.Interface)
		if !ok {
			usage.discard = true
			return
		}
		for i := 0; i < iface.NumMethods(); i++ {
			m := iface.Method(i)
			usage.calls[m.Name()] = struct{}{}
		}
	} else if t, ok := c.TypeOf(e).(*types.Signature); ok {
		// using func
		c.discardFuncs[t] = struct{}{}
	}
}

func (c *Checker) addAssign(to, from ast.Expr) {
	pto := c.varUsage(to)
	pfrom := c.varUsage(from)
	if pto == nil || pfrom == nil {
		// either isn't interesting
		return
	}
	pfrom.assigned[pto] = struct{}{}
}

func (c *Checker) discard(e ast.Expr) {
	if usage := c.varUsage(e); usage != nil {
		usage.discard = true
	}
}

func (c *Checker) comparedWith(e, with ast.Expr) {
	if _, ok := with.(*ast.BasicLit); ok {
		c.discard(e)
	}
}

func (c *Checker) Visit(node ast.Node) ast.Visitor {
	switch x := node.(type) {
	case *ast.SelectorExpr:
		if _, ok := c.TypeOf(x.Sel).(*types.Signature); !ok {
			c.discard(x.X)
		}
	case *ast.StarExpr:
		c.discard(x.X)
	case *ast.UnaryExpr:
		c.discard(x.X)
	case *ast.IndexExpr:
		c.discard(x.X)
	case *ast.IncDecStmt:
		c.discard(x.X)
	case *ast.BinaryExpr:
		switch x.Op {
		case token.EQL, token.NEQ:
			c.comparedWith(x.X, x.Y)
			c.comparedWith(x.Y, x.X)
		default:
			c.discard(x.X)
			c.discard(x.Y)
		}
	case *ast.ValueSpec:
		for _, val := range x.Values {
			c.addUsed(val, c.TypeOf(x.Type))
		}
	case *ast.AssignStmt:
		for i, val := range x.Rhs {
			left := x.Lhs[i]
			if x.Tok == token.ASSIGN {
				c.addUsed(val, c.TypeOf(left))
			}
			c.addAssign(left, val)
		}
	case *ast.CompositeLit:
		for i, e := range x.Elts {
			switch y := e.(type) {
			case *ast.KeyValueExpr:
				c.addUsed(y.Key, c.TypeOf(y.Value))
				c.addUsed(y.Value, c.TypeOf(y.Key))
			case *ast.Ident:
				c.addUsed(y, compositeIdentType(c.TypeOf(x), i))
			}
		}
	case *ast.CallExpr:
		switch y := c.TypeOf(x.Fun).Underlying().(type) {
		case *types.Signature:
			c.onMethodCall(x, y)
		default:
			// type conversion
			if len(x.Args) == 1 {
				c.addUsed(x.Args[0], y)
			}
		}
	}
	return c
}

func compositeIdentType(t types.Type, i int) types.Type {
	switch x := t.(type) {
	case *types.Named:
		return compositeIdentType(x.Underlying(), i)
	case *types.Struct:
		return x.Field(i).Type()
	case *types.Array:
		return x.Elem()
	case *types.Slice:
		return x.Elem()
	}
	return nil
}

func (c *Checker) onMethodCall(ce *ast.CallExpr, sign *types.Signature) {
	for i, e := range ce.Args {
		paramObj, t := paramVarAndType(sign, i)
		// Don't if this is a parameter being re-used as itself
		// in a recursive call
		if id, ok := e.(*ast.Ident); ok {
			if paramObj == c.ObjectOf(id) {
				continue
			}
		}
		c.addUsed(e, t)
	}
	sel, ok := ce.Fun.(*ast.SelectorExpr)
	if !ok {
		return
	}
	// receiver func call on the left side
	if usage := c.varUsage(sel.X); usage != nil {
		usage.calls[sel.Sel.Name] = struct{}{}
	}
}

func (fd *funcDecl) paramGroups() [][]*types.Var {
	astList := fd.astDecl.Type.Params.List
	groups := make([][]*types.Var, len(astList))
	signIndex := 0
	for i, field := range astList {
		group := make([]*types.Var, len(field.Names))
		for j := range field.Names {
			group[j] = fd.ssaFn.Signature.Params().At(signIndex)
			signIndex++
		}
		groups[i] = group
	}
	return groups
}

func (c *Checker) packageIssues() []lint.Issue {
	var issues []lint.Issue
	for _, fd := range c.funcs {
		if _, e := c.discardFuncs[fd.ssaFn.Signature]; e {
			continue
		}
		for _, group := range fd.paramGroups() {
			issues = append(issues, c.groupIssues(fd, group)...)
		}
	}
	return issues
}

type Issue struct {
	pos token.Pos
	msg string
}

func (i Issue) Pos() token.Pos  { return i.pos }
func (i Issue) Message() string { return i.msg }

func (c *Checker) groupIssues(fd *funcDecl, group []*types.Var) []lint.Issue {
	var issues []lint.Issue
	for _, param := range group {
		usage := c.vars[param]
		if usage == nil {
			return nil
		}
		newType := c.paramNewType(fd.astDecl.Name.Name, param, usage)
		if newType == "" {
			return nil
		}
		issues = append(issues, Issue{
			pos: param.Pos(),
			msg: fmt.Sprintf("%s can be %s", param.Name(), newType),
		})
	}
	return issues
}

func willAddAllocation(t types.Type) bool {
	switch t.Underlying().(type) {
	case *types.Pointer, *types.Interface:
		return false
	}
	return true
}

func (c *Checker) paramNewType(funcName string, param *types.Var, usage *varUsage) string {
	t := param.Type()
	if !ast.IsExported(funcName) && willAddAllocation(t) {
		return ""
	}
	if named := typeNamed(t); named != nil {
		tname := named.Obj().Name()
		vname := param.Name()
		if mentionsName(funcName, tname) || mentionsName(funcName, vname) {
			return ""
		}
	}
	ifname, iftype := c.interfaceMatching(param, usage)
	if ifname == "" {
		return ""
	}
	if types.IsInterface(t.Underlying()) {
		if have := funcMapString(typeFuncMap(t)); have == iftype {
			return ""
		}
	}
	return ifname
}
<|endoftext|>


<commit_before>package main

import (
	"regexp"
	"strconv"
	"time"
)

func CheckEmail(e string) bool {
	reg := regexp.MustCompile(`^(\w)+([\.\-]\w+)*@(\w)+((\.\w+)+)$`)
	return reg.MatchString(e)
}

func CheckDomain(domain string) bool {
	reg := regexp.MustCompile(`^(http://|https://)?[0-9a-zA-Z]+[0-9a-zA-Z\.-]*\.[a-zA-Z]{2,4}$`)
	return reg.MatchString(domain)
}

func CheckSMS(sms string) bool {
	reg := regexp.MustCompile(`^\+\d+$`)
	return reg.MatchString(sms)
}

func CheckPwd(pwd string) bool {
	return len(pwd) > 5 && len(pwd) < 19
}

func CheckDate(date string) time.Time {
	if t, err := time.Parse("2006-01-02 15:04", date); err == nil {
		return t
	}
	return time.Now()
}

func CheckSerieID(sid string) int32 {
	if id, err := strconv.Atoi(sid); err == nil {
		return int32(id)
	}
	return 0
}

func CheckBool(str string) bool {
	return str == "true" || str == "1"
}

func CheckPublish(do string) bool {
	return do == "publish"
}
<commit_msg>update time parse<commit_after>package main

import (
	"regexp"
	"strconv"
	"time"
)

func CheckEmail(e string) bool {
	reg := regexp.MustCompile(`^(\w)+([\.\-]\w+)*@(\w)+((\.\w+)+)$`)
	return reg.MatchString(e)
}

func CheckDomain(domain string) bool {
	reg := regexp.MustCompile(`^(http://|https://)?[0-9a-zA-Z]+[0-9a-zA-Z\.-]*\.[a-zA-Z]{2,4}$`)
	return reg.MatchString(domain)
}

func CheckSMS(sms string) bool {
	reg := regexp.MustCompile(`^\+\d+$`)
	return reg.MatchString(sms)
}

func CheckPwd(pwd string) bool {
	return len(pwd) > 5 && len(pwd) < 19
}

func CheckDate(date string) time.Time {
	if t, err := time.ParseInLocation("2006-01-02 15:04", date, time.Local); err == nil {
		return t
	}
	return time.Now()
}

func CheckSerieID(sid string) int32 {
	if id, err := strconv.Atoi(sid); err == nil {
		return int32(id)
	}
	return 0
}

func CheckBool(str string) bool {
	return str == "true" || str == "1"
}

func CheckPublish(do string) bool {
	return do == "publish"
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"net"
	"time"

	"github.com/lowstz/slackhookgo"
)

type alertMessage struct {
	url   string
	text  string
	color string
}

type connection struct {
	protocol string
	host     string
	port     string
	address  string
}

func checkError(err error) (int) {
	if err == nil {
		return 0
	} else {
		log.Printf("error: %s", err)
		return 1
	}

}

func (c *connection) conn() (net.Conn, error) {
	conn, err := net.DialTimeout(c.protocol, c.address, 3*time.Second)
	checkError(err)
	return conn, err
}

func (a *alertMessage) sentSlack() {
	err := slackhookgo.Send(
		a.url,
		slackhookgo.NewSlackMessage(
			"username",
			"backup",
		).AddAttachment(
			slackhookgo.MessageAttachment{
				Color: a.color,
				Text:  a.text,
				Title: "<!channel>",
			},
		),
	)
	checkError(err)
}

func main() {
	var (
		k 	string
		t	string
		lastE	int = -1
		protocol = flag.String("protocol", "tcp", "protocol tcp/udp")
		host     = flag.String("host", "ya.ru", "destination host")
		port     = flag.String("port", "80", "destination port")
		interval = flag.Uint("interval", 5, "interval check seconds")
		url      = flag.String("url", "", "hook url")
	)

	flag.Parse()

	for {
		c := connection{
			protocol: *protocol,
			address:  fmt.Sprintf("%s:%s", *host, *port),
		}
		conn, err := c.conn()
		e := checkError(err)
                if e != lastE {
                        if e == 0 {		// normal
                                k = "good"
                                t = "reachable"
                                conn.Close()
                        } else {		// not normal
                                k = "danger"
                                t = "unreachable"
                        }
                        lastE = e		// key of success
                        am := alertMessage{
                                color:  k,
				text:   fmt.Sprintf("Destination host %s:%s %s\n", *host, *port, t),
                                url:    *url,
                        }
                        am.sentSlack()
                }
		time.Sleep(time.Duration(*interval) * time.Second)
	}
}
<commit_msg>upd checkError <commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"net"
	"time"

	"github.com/lowstz/slackhookgo"
)

type alertMessage struct {
	url   string
	text  string
	color string
}

type connection struct {
	protocol string
	host     string
	port     string
	address  string
}

func checkError(err error) (int) {
	if err == nil {
		return 0
	}
		log.Printf("error: %s", err)
		return 1
}

func (c *connection) conn() (net.Conn, error) {
	conn, err := net.DialTimeout(c.protocol, c.address, 3*time.Second)
	checkError(err)
	return conn, err
}

func (a *alertMessage) sentSlack() {
	err := slackhookgo.Send(
		a.url,
		slackhookgo.NewSlackMessage(
			"username",
			"backup",
		).AddAttachment(
			slackhookgo.MessageAttachment{
				Color: a.color,
				Text:  a.text,
				Title: "<!channel>",
			},
		),
	)
	checkError(err)
}

func main() {
	var (
		k 	string
		t	string
		lastE	int = -1
		protocol = flag.String("protocol", "tcp", "protocol tcp/udp")
		host     = flag.String("host", "ya.ru", "destination host")
		port     = flag.String("port", "80", "destination port")
		interval = flag.Uint("interval", 5, "interval check seconds")
		url      = flag.String("url", "", "hook url")
	)

	flag.Parse()

	for {
		c := connection{
			protocol: *protocol,
			address:  fmt.Sprintf("%s:%s", *host, *port),
		}
		conn, err := c.conn()
		e := checkError(err)
                if e != lastE {
                        if e == 0 {		// normal
                                k = "good"
                                t = "reachable"
                                conn.Close()
                        } else {		// not normal
                                k = "danger"
                                t = "unreachable"
                        }
                        lastE = e		// key of success
                        am := alertMessage{
                                color:  k,
				text:   fmt.Sprintf("Destination host %s:%s %s\n", *host, *port, t),
                                url:    *url,
                        }
                        am.sentSlack()
                }
		time.Sleep(time.Duration(*interval) * time.Second)
	}
}
<|endoftext|>


<commit_before>package aws

import (
	"encoding/json"
	"fmt"
	"os"
	"testing"

	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
)

func TestAccDataSourceAwsPricingProduct_ec2(t *testing.T) {
	oldRegion := os.Getenv("AWS_DEFAULT_REGION")
	os.Setenv("AWS_DEFAULT_REGION", "us-east-1")
	defer os.Setenv("AWS_DEFAULT_REGION", oldRegion)
	resource.ParallelTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccDataSourceAwsPricingProductConfigEc2("test", "c5.large"),
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttrSet("data.aws_pricing_product.test", "result"),
					testAccPricingCheckValueIsJSON("data.aws_pricing_product.test"),
				),
			},
		},
	})
}

func TestAccDataSourceAwsPricingProduct_redshift(t *testing.T) {
	oldRegion := os.Getenv("AWS_DEFAULT_REGION")
	os.Setenv("AWS_DEFAULT_REGION", "us-east-1")
	defer os.Setenv("AWS_DEFAULT_REGION", oldRegion)
	resource.ParallelTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccDataSourceAwsPricingProductConfigRedshift(),
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttrSet("data.aws_pricing_product.test", "result"),
					testAccPricingCheckValueIsJSON("data.aws_pricing_product.test"),
				),
			},
		},
	})
}

func testAccDataSourceAwsPricingProductConfigEc2(dataName string, instanceType string) string {
	return fmt.Sprintf(`data "aws_pricing_product" "%s" {
		service_code = "AmazonEC2"

		filters = [
		  {
			field = "instanceType"
			value = "%s"
		  },
		  {
			field = "operatingSystem"
			value = "Linux"
		  },
		  {
			field = "location"
			value = "US East (N. Virginia)"
		  },
		  {
			field = "preInstalledSw"
			value = "NA"
		  },
		  {
			field = "licenseModel"
			value = "No License required"
		  },
		  {
			field = "tenancy"
			value = "Shared"
		  },
		]
}
`, dataName, instanceType)
}

func testAccDataSourceAwsPricingProductConfigRedshift() string {
	return fmt.Sprintf(`data "aws_pricing_product" "test" {
		service_code = "AmazonRedshift"

		filters = [
		  {
			field = "instanceType"
			value = "ds1.xlarge"
			},
			{
			field = "location"
			value = "US East (N. Virginia)"
		  },
		]
}
`)
}

func testAccPricingCheckValueIsJSON(data string) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[data]

		if !ok {
			return fmt.Errorf("Can't find resource: %s", data)
		}

		result := rs.Primary.Attributes["result"]
		var objmap map[string]*json.RawMessage

		if err := json.Unmarshal([]byte(result), &objmap); err != nil {
			return fmt.Errorf("%s result value (%s) is not JSON: %s", data, result, err)
		}

		if len(objmap) == 0 {
			return fmt.Errorf("%s result value (%s) unmarshalling resulted in an empty map", data, result)
		}

		return nil
	}
}
<commit_msg>tests/data-source/aws_pricing_product: Add capacitystatus filter for EC2 test configuration<commit_after>package aws

import (
	"encoding/json"
	"fmt"
	"os"
	"testing"

	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
)

func TestAccDataSourceAwsPricingProduct_ec2(t *testing.T) {
	oldRegion := os.Getenv("AWS_DEFAULT_REGION")
	os.Setenv("AWS_DEFAULT_REGION", "us-east-1")
	defer os.Setenv("AWS_DEFAULT_REGION", oldRegion)
	resource.ParallelTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccDataSourceAwsPricingProductConfigEc2("test", "c5.large"),
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttrSet("data.aws_pricing_product.test", "result"),
					testAccPricingCheckValueIsJSON("data.aws_pricing_product.test"),
				),
			},
		},
	})
}

func TestAccDataSourceAwsPricingProduct_redshift(t *testing.T) {
	oldRegion := os.Getenv("AWS_DEFAULT_REGION")
	os.Setenv("AWS_DEFAULT_REGION", "us-east-1")
	defer os.Setenv("AWS_DEFAULT_REGION", oldRegion)
	resource.ParallelTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccDataSourceAwsPricingProductConfigRedshift(),
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttrSet("data.aws_pricing_product.test", "result"),
					testAccPricingCheckValueIsJSON("data.aws_pricing_product.test"),
				),
			},
		},
	})
}

func testAccDataSourceAwsPricingProductConfigEc2(dataName string, instanceType string) string {
	return fmt.Sprintf(`data "aws_pricing_product" "%s" {
		service_code = "AmazonEC2"

		filters = [
		  {
			field = "instanceType"
			value = "%s"
		  },
		  {
			field = "operatingSystem"
			value = "Linux"
		  },
		  {
			field = "location"
			value = "US East (N. Virginia)"
		  },
		  {
			field = "preInstalledSw"
			value = "NA"
		  },
		  {
			field = "licenseModel"
			value = "No License required"
		  },
		  {
			field = "tenancy"
			value = "Shared"
		  },
		  {
			field = "capacitystatus"
			value = "Used"
		  },
		]
}
`, dataName, instanceType)
}

func testAccDataSourceAwsPricingProductConfigRedshift() string {
	return fmt.Sprintf(`data "aws_pricing_product" "test" {
		service_code = "AmazonRedshift"

		filters = [
		  {
			field = "instanceType"
			value = "ds1.xlarge"
			},
			{
			field = "location"
			value = "US East (N. Virginia)"
		  },
		]
}
`)
}

func testAccPricingCheckValueIsJSON(data string) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[data]

		if !ok {
			return fmt.Errorf("Can't find resource: %s", data)
		}

		result := rs.Primary.Attributes["result"]
		var objmap map[string]*json.RawMessage

		if err := json.Unmarshal([]byte(result), &objmap); err != nil {
			return fmt.Errorf("%s result value (%s) is not JSON: %s", data, result, err)
		}

		if len(objmap) == 0 {
			return fmt.Errorf("%s result value (%s) unmarshalling resulted in an empty map", data, result)
		}

		return nil
	}
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"strings"
	"testing"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/apigatewayv2"
	"github.com/hashicorp/terraform-plugin-sdk/helper/acctest"
	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/terraform"
)

func TestAccAWSAPIGatewayV2Model_basic(t *testing.T) {
	var apiId string
	var v apigatewayv2.GetModelOutput
	resourceName := "aws_apigatewayv2_model.test"
	rName := strings.ReplaceAll(acctest.RandomWithPrefix("tf-acc-test"), "-", "")

	schema := `
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "ExampleModel",
  "type": "object",
  "properties": {
    "id": {
      "type": "string"
    }
  }
}
`

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSAPIGatewayV2ModelDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSAPIGatewayV2ModelConfig_basic(rName, schema),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSAPIGatewayV2ModelExists(resourceName, &apiId, &v),
					resource.TestCheckResourceAttr(resourceName, "content_type", "application/json"),
					resource.TestCheckResourceAttr(resourceName, "description", ""),
					resource.TestCheckResourceAttr(resourceName, "name", rName),
					resource.TestCheckResourceAttr(resourceName, "schema", schema),
				),
			},
			{
				ResourceName:      resourceName,
				ImportStateIdFunc: testAccAWSAPIGatewayV2ModelImportStateIdFunc(resourceName),
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccAWSAPIGatewayV2Model_disappears(t *testing.T) {
	var apiId string
	var v apigatewayv2.GetModelOutput
	resourceName := "aws_apigatewayv2_model.test"
	rName := strings.ReplaceAll(acctest.RandomWithPrefix("tf-acc-test"), "-", "")

	schema := `
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "ExampleModel",
  "type": "object",
  "properties": {
    "id": {
      "type": "string"
    }
  }
}
`

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSAPIGatewayV2ModelDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSAPIGatewayV2ModelConfig_basic(rName, schema),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSAPIGatewayV2ModelExists(resourceName, &apiId, &v),
					testAccCheckAWSAPIGatewayV2ModelDisappears(&apiId, &v),
				),
				ExpectNonEmptyPlan: true,
			},
		},
	})
}

func TestAccAWSAPIGatewayV2Model_AllAttributes(t *testing.T) {
	var apiId string
	var v apigatewayv2.GetModelOutput
	resourceName := "aws_apigatewayv2_model.test"
	rName := strings.ReplaceAll(acctest.RandomWithPrefix("tf-acc-test"), "-", "")

	schema1 := `
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "ExampleModel1",
  "type": "object",
  "properties": {
    "id": {
      "type": "string"
    }
  }
}
`
	schema2 := `
	{
		"$schema": "http://json-schema.org/draft-04/schema#",
		"title": "ExampleModel",
		"type": "object",
		"properties": {
		  "ids": {
			"type": "array",
			"items":{
			  "type": "integer"
			}
		  }
		}
	  }
`

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSAPIGatewayV2ModelDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSAPIGatewayV2ModelConfig_allAttributes(rName, schema1),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSAPIGatewayV2ModelExists(resourceName, &apiId, &v),
					resource.TestCheckResourceAttr(resourceName, "content_type", "text/x-json"),
					resource.TestCheckResourceAttr(resourceName, "description", "test"),
					resource.TestCheckResourceAttr(resourceName, "name", rName),
					resource.TestCheckResourceAttr(resourceName, "schema", schema1),
				),
			},
			{
				Config: testAccAWSAPIGatewayV2ModelConfig_basic(rName, schema2),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSAPIGatewayV2ModelExists(resourceName, &apiId, &v),
					resource.TestCheckResourceAttr(resourceName, "content_type", "application/json"),
					resource.TestCheckResourceAttr(resourceName, "description", ""),
					resource.TestCheckResourceAttr(resourceName, "name", rName),
					resource.TestCheckResourceAttr(resourceName, "schema", schema2),
				),
			},
			{
				Config: testAccAWSAPIGatewayV2ModelConfig_allAttributes(rName, schema1),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSAPIGatewayV2ModelExists(resourceName, &apiId, &v),
					resource.TestCheckResourceAttr(resourceName, "content_type", "text/x-json"),
					resource.TestCheckResourceAttr(resourceName, "description", "test"),
					resource.TestCheckResourceAttr(resourceName, "name", rName),
					resource.TestCheckResourceAttr(resourceName, "schema", schema1),
				),
			},
			{
				ResourceName:      resourceName,
				ImportStateIdFunc: testAccAWSAPIGatewayV2ModelImportStateIdFunc(resourceName),
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func testAccCheckAWSAPIGatewayV2ModelDestroy(s *terraform.State) error {
	conn := testAccProvider.Meta().(*AWSClient).apigatewayv2conn

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "aws_apigatewayv2_model" {
			continue
		}

		_, err := conn.GetModel(&apigatewayv2.GetModelInput{
			ApiId:   aws.String(rs.Primary.Attributes["api_id"]),
			ModelId: aws.String(rs.Primary.ID),
		})
		if isAWSErr(err, apigatewayv2.ErrCodeNotFoundException, "") {
			continue
		}
		if err != nil {
			return err
		}

		return fmt.Errorf("API Gateway v2 model %s still exists", rs.Primary.ID)
	}

	return nil
}

func testAccCheckAWSAPIGatewayV2ModelDisappears(apiId *string, v *apigatewayv2.GetModelOutput) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		conn := testAccProvider.Meta().(*AWSClient).apigatewayv2conn

		_, err := conn.DeleteModel(&apigatewayv2.DeleteModelInput{
			ApiId:   apiId,
			ModelId: v.ModelId,
		})

		return err
	}
}

func testAccCheckAWSAPIGatewayV2ModelExists(n string, vApiId *string, v *apigatewayv2.GetModelOutput) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No API Gateway v2 model ID is set")
		}

		conn := testAccProvider.Meta().(*AWSClient).apigatewayv2conn

		apiId := aws.String(rs.Primary.Attributes["api_id"])
		resp, err := conn.GetModel(&apigatewayv2.GetModelInput{
			ApiId:   apiId,
			ModelId: aws.String(rs.Primary.ID),
		})
		if err != nil {
			return err
		}

		*vApiId = *apiId
		*v = *resp

		return nil
	}
}

func testAccAWSAPIGatewayV2ModelImportStateIdFunc(resourceName string) resource.ImportStateIdFunc {
	return func(s *terraform.State) (string, error) {
		rs, ok := s.RootModule().Resources[resourceName]
		if !ok {
			return "", fmt.Errorf("Not Found: %s", resourceName)
		}

		return fmt.Sprintf("%s/%s", rs.Primary.Attributes["api_id"], rs.Primary.ID), nil
	}
}

func testAccAWSAPIGatewayV2ModelConfig_api(rName string) string {
	return fmt.Sprintf(`
resource "aws_apigatewayv2_api" "test" {
  name                       = %[1]q
  protocol_type              = "WEBSOCKET"
  route_selection_expression = "$request.body.action"
}
`, rName)
}

func testAccAWSAPIGatewayV2ModelConfig_basic(rName, schema string) string {
	return testAccAWSAPIGatewayV2ModelConfig_api(rName) + fmt.Sprintf(`
resource "aws_apigatewayv2_model" "test" {
  api_id       = "${aws_apigatewayv2_api.test.id}"
  content_type = "application/json"
  name         = %[1]q
  schema       = %[2]q
}
`, rName, schema)
}

func testAccAWSAPIGatewayV2ModelConfig_allAttributes(rName, schema string) string {
	return testAccAWSAPIGatewayV2ModelConfig_api(rName) + fmt.Sprintf(`
resource "aws_apigatewayv2_model" "test" {
  api_id       = "${aws_apigatewayv2_api.test.id}"
  content_type = "text/x-json"
  name         = %[1]q
  description  = "test"
  schema       = %[2]q
}
`, rName, schema)
}
<commit_msg>Use 'testAccCheckResourceAttrEquivalentJSON'.<commit_after>package aws

import (
	"fmt"
	"strings"
	"testing"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/apigatewayv2"
	"github.com/hashicorp/terraform-plugin-sdk/helper/acctest"
	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/terraform"
)

func TestAccAWSAPIGatewayV2Model_basic(t *testing.T) {
	var apiId string
	var v apigatewayv2.GetModelOutput
	resourceName := "aws_apigatewayv2_model.test"
	rName := strings.ReplaceAll(acctest.RandomWithPrefix("tf-acc-test"), "-", "")

	schema := `
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "ExampleModel",
  "type": "object",
  "properties": {
    "id": {
      "type": "string"
    }
  }
}
`

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSAPIGatewayV2ModelDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSAPIGatewayV2ModelConfig_basic(rName, schema),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSAPIGatewayV2ModelExists(resourceName, &apiId, &v),
					resource.TestCheckResourceAttr(resourceName, "content_type", "application/json"),
					resource.TestCheckResourceAttr(resourceName, "description", ""),
					resource.TestCheckResourceAttr(resourceName, "name", rName),
					testAccCheckResourceAttrEquivalentJSON(resourceName, "schema", schema),
				),
			},
			{
				ResourceName:      resourceName,
				ImportStateIdFunc: testAccAWSAPIGatewayV2ModelImportStateIdFunc(resourceName),
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccAWSAPIGatewayV2Model_disappears(t *testing.T) {
	var apiId string
	var v apigatewayv2.GetModelOutput
	resourceName := "aws_apigatewayv2_model.test"
	rName := strings.ReplaceAll(acctest.RandomWithPrefix("tf-acc-test"), "-", "")

	schema := `
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "ExampleModel",
  "type": "object",
  "properties": {
    "id": {
      "type": "string"
    }
  }
}
`

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSAPIGatewayV2ModelDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSAPIGatewayV2ModelConfig_basic(rName, schema),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSAPIGatewayV2ModelExists(resourceName, &apiId, &v),
					testAccCheckAWSAPIGatewayV2ModelDisappears(&apiId, &v),
				),
				ExpectNonEmptyPlan: true,
			},
		},
	})
}

func TestAccAWSAPIGatewayV2Model_AllAttributes(t *testing.T) {
	var apiId string
	var v apigatewayv2.GetModelOutput
	resourceName := "aws_apigatewayv2_model.test"
	rName := strings.ReplaceAll(acctest.RandomWithPrefix("tf-acc-test"), "-", "")

	schema1 := `
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "ExampleModel1",
  "type": "object",
  "properties": {
    "id": {
      "type": "string"
    }
  }
}
`
	schema2 := `
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "ExampleModel",
  "type": "object",
  "properties": {
    "ids": {
      "type": "array",
        "items":{
          "type": "integer"
        }
    }
  }
}
`

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSAPIGatewayV2ModelDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccAWSAPIGatewayV2ModelConfig_allAttributes(rName, schema1),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSAPIGatewayV2ModelExists(resourceName, &apiId, &v),
					resource.TestCheckResourceAttr(resourceName, "content_type", "text/x-json"),
					resource.TestCheckResourceAttr(resourceName, "description", "test"),
					resource.TestCheckResourceAttr(resourceName, "name", rName),
					testAccCheckResourceAttrEquivalentJSON(resourceName, "schema", schema1),
				),
			},
			{
				Config: testAccAWSAPIGatewayV2ModelConfig_basic(rName, schema2),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSAPIGatewayV2ModelExists(resourceName, &apiId, &v),
					resource.TestCheckResourceAttr(resourceName, "content_type", "application/json"),
					resource.TestCheckResourceAttr(resourceName, "description", ""),
					resource.TestCheckResourceAttr(resourceName, "name", rName),
					testAccCheckResourceAttrEquivalentJSON(resourceName, "schema", schema2),
				),
			},
			{
				Config: testAccAWSAPIGatewayV2ModelConfig_allAttributes(rName, schema1),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSAPIGatewayV2ModelExists(resourceName, &apiId, &v),
					resource.TestCheckResourceAttr(resourceName, "content_type", "text/x-json"),
					resource.TestCheckResourceAttr(resourceName, "description", "test"),
					resource.TestCheckResourceAttr(resourceName, "name", rName),
					testAccCheckResourceAttrEquivalentJSON(resourceName, "schema", schema1),
				),
			},
			{
				ResourceName:      resourceName,
				ImportStateIdFunc: testAccAWSAPIGatewayV2ModelImportStateIdFunc(resourceName),
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func testAccCheckAWSAPIGatewayV2ModelDestroy(s *terraform.State) error {
	conn := testAccProvider.Meta().(*AWSClient).apigatewayv2conn

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "aws_apigatewayv2_model" {
			continue
		}

		_, err := conn.GetModel(&apigatewayv2.GetModelInput{
			ApiId:   aws.String(rs.Primary.Attributes["api_id"]),
			ModelId: aws.String(rs.Primary.ID),
		})
		if isAWSErr(err, apigatewayv2.ErrCodeNotFoundException, "") {
			continue
		}
		if err != nil {
			return err
		}

		return fmt.Errorf("API Gateway v2 model %s still exists", rs.Primary.ID)
	}

	return nil
}

func testAccCheckAWSAPIGatewayV2ModelDisappears(apiId *string, v *apigatewayv2.GetModelOutput) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		conn := testAccProvider.Meta().(*AWSClient).apigatewayv2conn

		_, err := conn.DeleteModel(&apigatewayv2.DeleteModelInput{
			ApiId:   apiId,
			ModelId: v.ModelId,
		})

		return err
	}
}

func testAccCheckAWSAPIGatewayV2ModelExists(n string, vApiId *string, v *apigatewayv2.GetModelOutput) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No API Gateway v2 model ID is set")
		}

		conn := testAccProvider.Meta().(*AWSClient).apigatewayv2conn

		apiId := aws.String(rs.Primary.Attributes["api_id"])
		resp, err := conn.GetModel(&apigatewayv2.GetModelInput{
			ApiId:   apiId,
			ModelId: aws.String(rs.Primary.ID),
		})
		if err != nil {
			return err
		}

		*vApiId = *apiId
		*v = *resp

		return nil
	}
}

func testAccAWSAPIGatewayV2ModelImportStateIdFunc(resourceName string) resource.ImportStateIdFunc {
	return func(s *terraform.State) (string, error) {
		rs, ok := s.RootModule().Resources[resourceName]
		if !ok {
			return "", fmt.Errorf("Not Found: %s", resourceName)
		}

		return fmt.Sprintf("%s/%s", rs.Primary.Attributes["api_id"], rs.Primary.ID), nil
	}
}

func testAccAWSAPIGatewayV2ModelConfig_api(rName string) string {
	return fmt.Sprintf(`
resource "aws_apigatewayv2_api" "test" {
  name                       = %[1]q
  protocol_type              = "WEBSOCKET"
  route_selection_expression = "$request.body.action"
}
`, rName)
}

func testAccAWSAPIGatewayV2ModelConfig_basic(rName, schema string) string {
	return testAccAWSAPIGatewayV2ModelConfig_api(rName) + fmt.Sprintf(`
resource "aws_apigatewayv2_model" "test" {
  api_id       = "${aws_apigatewayv2_api.test.id}"
  content_type = "application/json"
  name         = %[1]q
  schema       = %[2]q
}
`, rName, schema)
}

func testAccAWSAPIGatewayV2ModelConfig_allAttributes(rName, schema string) string {
	return testAccAWSAPIGatewayV2ModelConfig_api(rName) + fmt.Sprintf(`
resource "aws_apigatewayv2_model" "test" {
  api_id       = "${aws_apigatewayv2_api.test.id}"
  content_type = "text/x-json"
  name         = %[1]q
  description  = "test"
  schema       = %[2]q
}
`, rName, schema)
}
<|endoftext|>


<commit_before>package mysql_test

import (
	"context"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp/cmpopts"

	"github.com/oinume/lekcije/backend/domain/config"
	"github.com/oinume/lekcije/backend/infrastructure/mysql"
	"github.com/oinume/lekcije/backend/internal/assertion"
	"github.com/oinume/lekcije/backend/internal/modeltest"
	"github.com/oinume/lekcije/backend/internal/mysqltest"
	"github.com/oinume/lekcije/backend/model2"
)

func Test_lessonRepository_FindAllByTeacherIDsDatetimeBetween(t *testing.T) {
	repo := mysql.NewLessonRepository(helper.DB(t).DB())
	repos := mysqltest.NewRepositories(helper.DB(t).DB())

	type testCase struct {
		teacherID uint
		fromDate  time.Time
		toDate    time.Time
		want      []*model2.Lesson
	}
	modeltest.NewFollowingTeacher()
	tests := map[string]struct {
		setup   func(ctx context.Context) *testCase
		wantErr bool
	}{
		"normal": {
			setup: func(ctx context.Context) *testCase {
				const teacherID = 1
				l1 := modeltest.NewLesson(func(l *model2.Lesson) {
					l.TeacherID = teacherID
					l.Datetime = time.Date(2022, 11, 1, 10, 0, 0, 0, config.DefaultVars.LocalLocation)
				})
				l2 := modeltest.NewLesson(func(l *model2.Lesson) {
					l.TeacherID = teacherID
					l.Datetime = time.Date(2022, 11, 2, 10, 30, 0, 0, config.DefaultVars.LocalLocation)
				})
				l3 := modeltest.NewLesson(func(l *model2.Lesson) {
					l.TeacherID = teacherID
					l.Datetime = time.Date(2022, 11, 5, 10, 30, 0, 0, config.DefaultVars.LocalLocation)
				})
				lessons := []*model2.Lesson{l1, l2, l3}
				repos.CreateLessons(ctx, t, lessons...)

				return &testCase{
					teacherID: teacherID,
					fromDate:  time.Date(2022, 11, 1, 10, 0, 0, 0, config.DefaultVars.LocalLocation),
					toDate:    time.Date(2022, 11, 2, 10, 30, 0, 0, config.DefaultVars.LocalLocation),
					want:      []*model2.Lesson{l1, l2}, // l3 doesn't hit
				}
			},
		},
		"no records": {
			setup: func(ctx context.Context) *testCase {
				const teacherID = 2
				l1 := modeltest.NewLesson(func(l *model2.Lesson) {
					l.TeacherID = teacherID
					l.Datetime = time.Date(2022, 11, 1, 10, 0, 0, 0, config.DefaultVars.LocalLocation)
				})
				lessons := []*model2.Lesson{l1}
				repos.CreateLessons(ctx, t, lessons...)

				return &testCase{
					teacherID: teacherID + 1,
					fromDate:  time.Date(2022, 12, 1, 10, 0, 0, 0, config.DefaultVars.LocalLocation),
					toDate:    time.Date(2022, 12, 2, 10, 30, 0, 0, config.DefaultVars.LocalLocation),
					want:      nil,
				}
			},
		},
	}
	for name, tt := range tests {
		t.Run(name, func(t *testing.T) {
			ctx := context.Background()
			tc := tt.setup(ctx)
			got, err := repo.FindAllByTeacherIDsDatetimeBetween(ctx, tc.teacherID, tc.fromDate, tc.toDate)
			if (err != nil) != tt.wantErr {
				t.Fatalf("FindAllByTeacherIDsDatetimeBetween() error = %v, wantErr %v", err, tt.wantErr)
			}
			assertion.AssertEqual(t, len(tc.want), len(got), "length of want and got is not same", cmpopts.EquateApproxTime(10*time.Second))
			assertion.AssertEqual(t, tc.want, got, "", cmpopts.EquateApproxTime(10*time.Second))
		})
	}
}
<commit_msg>Add unit test for<commit_after>package mysql_test

import (
	"context"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp/cmpopts"

	"github.com/oinume/lekcije/backend/domain/config"
	"github.com/oinume/lekcije/backend/infrastructure/mysql"
	"github.com/oinume/lekcije/backend/internal/assertion"
	"github.com/oinume/lekcije/backend/internal/modeltest"
	"github.com/oinume/lekcije/backend/internal/mysqltest"
	"github.com/oinume/lekcije/backend/model2"
	"github.com/oinume/lekcije/backend/randoms"
)

func Test_lessonRepository_FindAllByTeacherIDsDatetimeBetween(t *testing.T) {
	repo := mysql.NewLessonRepository(helper.DB(t).DB())
	repos := mysqltest.NewRepositories(helper.DB(t).DB())

	type testCase struct {
		teacherID uint
		fromDate  time.Time
		toDate    time.Time
		want      []*model2.Lesson
	}
	modeltest.NewFollowingTeacher()
	tests := map[string]struct {
		setup   func(ctx context.Context) *testCase
		wantErr bool
	}{
		"normal": {
			setup: func(ctx context.Context) *testCase {
				teacherID := uint(randoms.MustNewInt64(10000000))
				l1 := modeltest.NewLesson(func(l *model2.Lesson) {
					l.TeacherID = teacherID
					l.Datetime = time.Date(2022, 11, 1, 10, 0, 0, 0, config.DefaultVars.LocalLocation)
				})
				l2 := modeltest.NewLesson(func(l *model2.Lesson) {
					l.TeacherID = teacherID
					l.Datetime = time.Date(2022, 11, 2, 10, 30, 0, 0, config.DefaultVars.LocalLocation)
				})
				l3 := modeltest.NewLesson(func(l *model2.Lesson) {
					l.TeacherID = teacherID
					l.Datetime = time.Date(2022, 11, 5, 10, 30, 0, 0, config.DefaultVars.LocalLocation)
				})
				lessons := []*model2.Lesson{l1, l2, l3}
				repos.CreateLessons(ctx, t, lessons...)

				return &testCase{
					teacherID: teacherID,
					fromDate:  time.Date(2022, 11, 1, 10, 0, 0, 0, config.DefaultVars.LocalLocation),
					toDate:    time.Date(2022, 11, 2, 10, 30, 0, 0, config.DefaultVars.LocalLocation),
					want:      []*model2.Lesson{l1, l2}, // l3 doesn't hit
				}
			},
		},
		"no records": {
			setup: func(ctx context.Context) *testCase {
				teacherID := uint(randoms.MustNewInt64(10000000))
				l1 := modeltest.NewLesson(func(l *model2.Lesson) {
					l.TeacherID = teacherID
					l.Datetime = time.Date(2022, 11, 1, 10, 0, 0, 0, config.DefaultVars.LocalLocation)
				})
				lessons := []*model2.Lesson{l1}
				repos.CreateLessons(ctx, t, lessons...)

				return &testCase{
					teacherID: teacherID + 1,
					fromDate:  time.Date(2022, 12, 1, 10, 0, 0, 0, config.DefaultVars.LocalLocation),
					toDate:    time.Date(2022, 12, 2, 10, 30, 0, 0, config.DefaultVars.LocalLocation),
					want:      nil,
				}
			},
		},
	}
	for name, tt := range tests {
		t.Run(name, func(t *testing.T) {
			ctx := context.Background()
			tc := tt.setup(ctx)
			got, err := repo.FindAllByTeacherIDsDatetimeBetween(ctx, tc.teacherID, tc.fromDate, tc.toDate)
			if (err != nil) != tt.wantErr {
				t.Fatalf("FindAllByTeacherIDsDatetimeBetween() error = %v, wantErr %v", err, tt.wantErr)
			}
			assertion.AssertEqual(t, len(tc.want), len(got), "length of want and got is not same", cmpopts.EquateApproxTime(10*time.Second))
			assertion.AssertEqual(t, tc.want, got, "", cmpopts.EquateApproxTime(10*time.Second))
		})
	}
}

func Test_lessonRepository_GetNewAvailableLessons(t *testing.T) {
	repo := mysql.NewLessonRepository(helper.DB(t).DB())

	type testCase struct {
		oldLessons []*model2.Lesson
		newLessons []*model2.Lesson
		want       []*model2.Lesson
	}
	tests := map[string]struct {
		setup func(ctx context.Context) *testCase
	}{
		"one available lessons": {
			setup: func(ctx context.Context) *testCase {
				teacherID := uint(randoms.MustNewInt64(100000))
				datetime := time.Date(2016, 10, 1, 14, 30, 0, 0, config.LocalLocation())
				lessons1 := newLessons(teacherID, datetime, "Reserved", 3)
				lessons2 := newLessons(teacherID, datetime, "Reserved", 3)
				lessons2[1].Status = "Available"
				return &testCase{
					oldLessons: lessons1,
					newLessons: lessons2,
					want: []*model2.Lesson{
						lessons2[1],
					},
				}
			},
		},
		"no available lessons": {
			setup: func(ctx context.Context) *testCase {
				teacherID := uint(randoms.MustNewInt64(100000))
				datetime := time.Date(2016, 10, 1, 14, 30, 0, 0, config.LocalLocation())
				lessons1 := newLessons(teacherID, datetime, "Reserved", 3)
				lessons2 := newLessons(teacherID, datetime, "Reserved", 3)
				// There are available lessons in old, means no difference between lessons1 and lessons2
				lessons1[0].Status = "Available"
				lessons2[0].Status = "Available"
				return &testCase{
					oldLessons: lessons1,
					newLessons: lessons2,
					want:       []*model2.Lesson{},
				}
			},
		},
	}
	for name, tt := range tests {
		t.Run(name, func(t *testing.T) {
			ctx := context.Background()
			tc := tt.setup(ctx)
			got := repo.GetNewAvailableLessons(ctx, tc.oldLessons, tc.newLessons)
			assertion.AssertEqual(t, len(tc.want), len(got), "length of lessons doesn't match")
			assertion.AssertEqual(t, tc.want, got, "")
		})
	}
}

func newLessons(teacherID uint, baseDatetime time.Time, status string, length int) []*model2.Lesson {
	lessons := make([]*model2.Lesson, length)
	now := time.Now().UTC()
	for i := range lessons {
		lessons[i] = &model2.Lesson{
			TeacherID: teacherID,
			Datetime:  baseDatetime.Add(time.Duration(i) * time.Hour),
			Status:    status,
			CreatedAt: now,
			UpdatedAt: now,
		}
	}
	return lessons
}
<|endoftext|>


<commit_before>// Copyright 2011 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.

// This file implements CGI from the perspective of a child
// process.

package cgi

import (
	"bufio"
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
)

// Request returns the HTTP request as represented in the current
// environment. This assumes the current program is being run
// by a web server in a CGI environment.
// The returned Request's Body is populated, if applicable.
func Request() (*http.Request, error) {
	r, err := RequestFromMap(envMap(os.Environ()))
	if err != nil {
		return nil, err
	}
	if r.ContentLength > 0 {
		r.Body = ioutil.NopCloser(io.LimitReader(os.Stdin, r.ContentLength))
	}
	return r, nil
}

func envMap(env []string) map[string]string {
	m := make(map[string]string)
	for _, kv := range env {
		if idx := strings.Index(kv, "="); idx != -1 {
			m[kv[:idx]] = kv[idx+1:]
		}
	}
	return m
}

// RequestFromMap creates an http.Request from CGI variables.
// The returned Request's Body field is not populated.
func RequestFromMap(params map[string]string) (*http.Request, error) {
	r := new(http.Request)
	r.Method = params["REQUEST_METHOD"]
	if r.Method == "" {
		return nil, errors.New("cgi: no REQUEST_METHOD in environment")
	}

	r.Proto = params["SERVER_PROTOCOL"]
	var ok bool
	r.ProtoMajor, r.ProtoMinor, ok = http.ParseHTTPVersion(r.Proto)
	if !ok {
		return nil, errors.New("cgi: invalid SERVER_PROTOCOL version")
	}

	r.Close = true
	r.Trailer = http.Header{}
	r.Header = http.Header{}

	r.Host = params["HTTP_HOST"]

	if lenstr := params["CONTENT_LENGTH"]; lenstr != "" {
		clen, err := strconv.ParseInt(lenstr, 10, 64)
		if err != nil {
			return nil, errors.New("cgi: bad CONTENT_LENGTH in environment: " + lenstr)
		}
		r.ContentLength = clen
	}

	if ct := params["CONTENT_TYPE"]; ct != "" {
		r.Header.Set("Content-Type", ct)
	}

	// Copy "HTTP_FOO_BAR" variables to "Foo-Bar" Headers
	for k, v := range params {
		if !strings.HasPrefix(k, "HTTP_") || k == "HTTP_HOST" {
			continue
		}
		r.Header.Add(strings.Replace(k[5:], "_", "-", -1), v)
	}

	// TODO: cookies.  parsing them isn't exported, though.

	uriStr := params["REQUEST_URI"]
	if uriStr == "" {
		// Fallback to SCRIPT_NAME, PATH_INFO and QUERY_STRING.
		uriStr = params["SCRIPT_NAME"] + params["PATH_INFO"]
		s := params["QUERY_STRING"]
		if s != "" {
			uriStr += "?" + s
		}
	}
	if r.Host != "" {
		// Hostname is provided, so we can reasonably construct a URL,
		// even if we have to assume 'http' for the scheme.
		rawurl := "http://" + r.Host + uriStr
		url, err := url.Parse(rawurl)
		if err != nil {
			return nil, errors.New("cgi: failed to parse host and REQUEST_URI into a URL: " + rawurl)
		}
		r.URL = url
	}
	// Fallback logic if we don't have a Host header or the URL
	// failed to parse
	if r.URL == nil {
		url, err := url.Parse(uriStr)
		if err != nil {
			return nil, errors.New("cgi: failed to parse REQUEST_URI into a URL: " + uriStr)
		}
		r.URL = url
	}

	// There's apparently a de-facto standard for this.
	// http://docstore.mik.ua/orelly/linux/cgi/ch03_02.htm#ch03-35636
	if s := params["HTTPS"]; s == "on" || s == "ON" || s == "1" {
		r.TLS = &tls.ConnectionState{HandshakeComplete: true}
	}

	// Request.RemoteAddr has its port set by Go's standard http
	// server, so we do here too. We don't have one, though, so we
	// use a dummy one.
	r.RemoteAddr = net.JoinHostPort(params["REMOTE_ADDR"], "0")

	return r, nil
}

// Serve executes the provided Handler on the currently active CGI
// request, if any. If there's no current CGI environment
// an error is returned. The provided handler may be nil to use
// http.DefaultServeMux.
func Serve(handler http.Handler) error {
	req, err := Request()
	if err != nil {
		return err
	}
	if handler == nil {
		handler = http.DefaultServeMux
	}
	rw := &response{
		req:    req,
		header: make(http.Header),
		bufw:   bufio.NewWriter(os.Stdout),
	}
	handler.ServeHTTP(rw, req)
	rw.Write(nil) // make sure a response is sent
	if err = rw.bufw.Flush(); err != nil {
		return err
	}
	return nil
}

type response struct {
	req        *http.Request
	header     http.Header
	bufw       *bufio.Writer
	headerSent bool
}

func (r *response) Flush() {
	r.bufw.Flush()
}

func (r *response) Header() http.Header {
	return r.header
}

func (r *response) Write(p []byte) (n int, err error) {
	if !r.headerSent {
		r.WriteHeader(http.StatusOK)
	}
	return r.bufw.Write(p)
}

func (r *response) WriteHeader(code int) {
	if r.headerSent {
		// Note: explicitly using Stderr, as Stdout is our HTTP output.
		fmt.Fprintf(os.Stderr, "CGI attempted to write header twice on request for %s", r.req.URL)
		return
	}
	r.headerSent = true
	fmt.Fprintf(r.bufw, "Status: %d %s\r\n", code, http.StatusText(code))

	// Set a default Content-Type
	if _, hasType := r.header["Content-Type"]; !hasType {
		r.header.Add("Content-Type", "text/html; charset=utf-8")
	}

	r.header.Write(r.bufw)
	r.bufw.WriteString("\r\n")
	r.bufw.Flush()
}
<commit_msg>all: use strings.IndexByte instead of Index where possible<commit_after>// Copyright 2011 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.

// This file implements CGI from the perspective of a child
// process.

package cgi

import (
	"bufio"
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
)

// Request returns the HTTP request as represented in the current
// environment. This assumes the current program is being run
// by a web server in a CGI environment.
// The returned Request's Body is populated, if applicable.
func Request() (*http.Request, error) {
	r, err := RequestFromMap(envMap(os.Environ()))
	if err != nil {
		return nil, err
	}
	if r.ContentLength > 0 {
		r.Body = ioutil.NopCloser(io.LimitReader(os.Stdin, r.ContentLength))
	}
	return r, nil
}

func envMap(env []string) map[string]string {
	m := make(map[string]string)
	for _, kv := range env {
		if idx := strings.IndexByte(kv, '='); idx != -1 {
			m[kv[:idx]] = kv[idx+1:]
		}
	}
	return m
}

// RequestFromMap creates an http.Request from CGI variables.
// The returned Request's Body field is not populated.
func RequestFromMap(params map[string]string) (*http.Request, error) {
	r := new(http.Request)
	r.Method = params["REQUEST_METHOD"]
	if r.Method == "" {
		return nil, errors.New("cgi: no REQUEST_METHOD in environment")
	}

	r.Proto = params["SERVER_PROTOCOL"]
	var ok bool
	r.ProtoMajor, r.ProtoMinor, ok = http.ParseHTTPVersion(r.Proto)
	if !ok {
		return nil, errors.New("cgi: invalid SERVER_PROTOCOL version")
	}

	r.Close = true
	r.Trailer = http.Header{}
	r.Header = http.Header{}

	r.Host = params["HTTP_HOST"]

	if lenstr := params["CONTENT_LENGTH"]; lenstr != "" {
		clen, err := strconv.ParseInt(lenstr, 10, 64)
		if err != nil {
			return nil, errors.New("cgi: bad CONTENT_LENGTH in environment: " + lenstr)
		}
		r.ContentLength = clen
	}

	if ct := params["CONTENT_TYPE"]; ct != "" {
		r.Header.Set("Content-Type", ct)
	}

	// Copy "HTTP_FOO_BAR" variables to "Foo-Bar" Headers
	for k, v := range params {
		if !strings.HasPrefix(k, "HTTP_") || k == "HTTP_HOST" {
			continue
		}
		r.Header.Add(strings.Replace(k[5:], "_", "-", -1), v)
	}

	// TODO: cookies.  parsing them isn't exported, though.

	uriStr := params["REQUEST_URI"]
	if uriStr == "" {
		// Fallback to SCRIPT_NAME, PATH_INFO and QUERY_STRING.
		uriStr = params["SCRIPT_NAME"] + params["PATH_INFO"]
		s := params["QUERY_STRING"]
		if s != "" {
			uriStr += "?" + s
		}
	}
	if r.Host != "" {
		// Hostname is provided, so we can reasonably construct a URL,
		// even if we have to assume 'http' for the scheme.
		rawurl := "http://" + r.Host + uriStr
		url, err := url.Parse(rawurl)
		if err != nil {
			return nil, errors.New("cgi: failed to parse host and REQUEST_URI into a URL: " + rawurl)
		}
		r.URL = url
	}
	// Fallback logic if we don't have a Host header or the URL
	// failed to parse
	if r.URL == nil {
		url, err := url.Parse(uriStr)
		if err != nil {
			return nil, errors.New("cgi: failed to parse REQUEST_URI into a URL: " + uriStr)
		}
		r.URL = url
	}

	// There's apparently a de-facto standard for this.
	// http://docstore.mik.ua/orelly/linux/cgi/ch03_02.htm#ch03-35636
	if s := params["HTTPS"]; s == "on" || s == "ON" || s == "1" {
		r.TLS = &tls.ConnectionState{HandshakeComplete: true}
	}

	// Request.RemoteAddr has its port set by Go's standard http
	// server, so we do here too. We don't have one, though, so we
	// use a dummy one.
	r.RemoteAddr = net.JoinHostPort(params["REMOTE_ADDR"], "0")

	return r, nil
}

// Serve executes the provided Handler on the currently active CGI
// request, if any. If there's no current CGI environment
// an error is returned. The provided handler may be nil to use
// http.DefaultServeMux.
func Serve(handler http.Handler) error {
	req, err := Request()
	if err != nil {
		return err
	}
	if handler == nil {
		handler = http.DefaultServeMux
	}
	rw := &response{
		req:    req,
		header: make(http.Header),
		bufw:   bufio.NewWriter(os.Stdout),
	}
	handler.ServeHTTP(rw, req)
	rw.Write(nil) // make sure a response is sent
	if err = rw.bufw.Flush(); err != nil {
		return err
	}
	return nil
}

type response struct {
	req        *http.Request
	header     http.Header
	bufw       *bufio.Writer
	headerSent bool
}

func (r *response) Flush() {
	r.bufw.Flush()
}

func (r *response) Header() http.Header {
	return r.header
}

func (r *response) Write(p []byte) (n int, err error) {
	if !r.headerSent {
		r.WriteHeader(http.StatusOK)
	}
	return r.bufw.Write(p)
}

func (r *response) WriteHeader(code int) {
	if r.headerSent {
		// Note: explicitly using Stderr, as Stdout is our HTTP output.
		fmt.Fprintf(os.Stderr, "CGI attempted to write header twice on request for %s", r.req.URL)
		return
	}
	r.headerSent = true
	fmt.Fprintf(r.bufw, "Status: %d %s\r\n", code, http.StatusText(code))

	// Set a default Content-Type
	if _, hasType := r.header["Content-Type"]; !hasType {
		r.header.Add("Content-Type", "text/html; charset=utf-8")
	}

	r.header.Write(r.bufw)
	r.bufw.WriteString("\r\n")
	r.bufw.Flush()
}
<|endoftext|>


<commit_before>package network

import (
	"bytes"
	"common"
	"testing"
	"time"
)

// a simple message handler
// stores the received message in the result field
// uses a simple channel to signal when handler has been called
type TestMsgHandler struct {
	id     common.Identifier
	result string
	done   chan bool
}

func (tmh *TestMsgHandler) Identifier() common.Identifier {
	return tmh.id
}

func (tmh *TestMsgHandler) HandleMessage(payload []byte) {
	tmh.result = string(payload)
	tmh.done <- true
}

// TestTCPSendMessage tests the NewTCPServer and SendMessage functions.
// NewTCPServer must properly initialize a TCP server.
// SendMessage must succesfully deliver a message.
func TestTCPSendMessage(t *testing.T) {
	// create TCPServer and add a message handler
	tcp, err := NewTCPServer(9988)
	if err != nil {
		t.Fatal("Failed to initialize TCPServer:", err)
	}
	defer tcp.Close()

	// create message handler and add it to the TCPServer
	tmh := new(TestMsgHandler)
	tmh.id = 1
	tmh.done = make(chan bool, 1)
	tcp.AddMessageHandler(tmh)

	// send a message to be echoed
	m := common.Message{common.Address{1, "localhost", 9988}, []byte("hello, world!")}
	err = tcp.SendMessage(&m)
	if err != nil {
		t.Fatal("Failed to send message:", err)
	}
	// wait for handler to be triggered
	<-tmh.done
	if tmh.result == "" {
		t.Fatal("Bad response: expected \"hello, world!\", got \"\"")
	}
	if tmh.result != "hello, world!" {
		t.Fatal("Bad response: expected \"hello, world!\", got \"" + tmh.result + "\"")
	}

	// send a message that should not trigger a MessageHandler
	tmh.result = ""
	m = common.Message{common.Address{2, "localhost", 9988}, []byte("won't be seen")}
	err = tcp.SendMessage(&m)
	if err != nil {
		t.Fatal("Failed to send message:", err)
	}
	// arbitrary wait, because <-tmh.done will block
	time.Sleep(10 * time.Millisecond)
	if tmh.result != "" {
		t.Fatal("Bad response: expected \"\", got \"" + tmh.result + "\"")
	}

	// send a message longer than 1024 bytes
	tmh.result = ""
	m = common.Message{common.Address{1, "localhost", 9988}, bytes.Repeat([]byte("b"), 9001)}
	err = tcp.SendMessage(&m)
	if err != nil {
		t.Fatal("Failed to send message:", err)
	}
	// wait for handler to be triggered
	<-tmh.done
	if len(tmh.result) != 9001 {
		t.Fatal("Bad response: expected 9001 bytes, got", len(tmh.result))
	}
}
<commit_msg>add test for SendFile()<commit_after>package network

import (
	"bytes"
	"common"
	"io"
	"io/ioutil"
	"os"
	"testing"
	"time"
)

// a simple message handler
// stores the received message in the result field
// uses a channel to signal when handler has been called
type TestMsgHandler struct {
	id     common.Identifier
	result string
	done   chan bool
}

func (tmh *TestMsgHandler) Identifier() common.Identifier {
	return tmh.id
}

func (tmh *TestMsgHandler) HandleMessage(payload []byte) {
	tmh.result = string(payload)
	tmh.done <- true
}

// TestTCPSendMessage tests the NewTCPServer and SendMessage functions.
// NewTCPServer must properly initialize a TCP server.
// SendMessage must succesfully deliver a message.
func TestTCPSendMessage(t *testing.T) {
	// create TCPServer and add a message handler
	tcp, err := NewTCPServer(9988)
	if err != nil {
		t.Fatal("Failed to initialize TCPServer:", err)
	}
	defer tcp.Close()

	// create message handler and add it to the TCPServer
	tmh := new(TestMsgHandler)
	tmh.id = 1
	tmh.done = make(chan bool, 1)
	tcp.AddMessageHandler(tmh)

	// send a message to be echoed
	m := common.Message{common.Address{1, "localhost", 9988}, []byte("hello, world!")}
	err = tcp.SendMessage(&m)
	if err != nil {
		t.Fatal("Failed to send message:", err)
	}

	// wait for handler to be triggered
	<-tmh.done

	if tmh.result == "" {
		t.Fatal("Bad response: expected \"hello, world!\", got \"\"")
	}
	if tmh.result != "hello, world!" {
		t.Fatal("Bad response: expected \"hello, world!\", got \"" + tmh.result + "\"")
	}

	// send a message that should not trigger a MessageHandler
	tmh.result = ""
	m = common.Message{common.Address{2, "localhost", 9988}, []byte("won't be seen")}
	err = tcp.SendMessage(&m)
	if err != nil {
		t.Fatal("Failed to send message:", err)
	}

	// arbitrary wait, because <-tmh.done will block
	time.Sleep(10 * time.Millisecond)

	if tmh.result != "" {
		t.Fatal("Bad response: expected \"\", got \"" + tmh.result + "\"")
	}

	// send a message longer than 1024 bytes
	tmh.result = ""
	m = common.Message{common.Address{1, "localhost", 9988}, bytes.Repeat([]byte("b"), 9001)}
	err = tcp.SendMessage(&m)
	if err != nil {
		t.Fatal("Failed to send message:", err)
	}

	// wait for handler to be triggered
	<-tmh.done

	if len(tmh.result) != 9001 {
		t.Fatal("Bad response: expected 9001 bytes, got", len(tmh.result))
	}
}

// a simple message handler
// writes the received data to a file handle
// uses a channel to signal when handler has been called
type TestFileHandler struct {
	id   common.Identifier
	file *os.File
	done chan bool
}

func (tfh *TestFileHandler) Identifier() common.Identifier {
	return tfh.id
}

func (tfh *TestFileHandler) HandleMessage(payload []byte) {
	tfh.file.Write(payload)
	tfh.done <- true
}

// TestTCPSendFile tests the NewTCPServer and SendFile functions.
// NewTCPServer must properly initialize a TCP server.
// SendFile must succesfully transfer a file.
func TestTCPSendFile(t *testing.T) {
	// create TCPServer and add a message handler
	tcp, err := NewTCPServer(9988)
	if err != nil {
		t.Fatal("Failed to initialize TCPServer:", err)
	}
	defer tcp.Close()

	// create message handler and add it to the TCPServer
	tfh := new(TestFileHandler)
	tfh.id = 1
	tfh.done = make(chan bool, 1)
	tfh.file, err = os.Create("OutputFile")
	if err != nil {
		t.Fatal("Failed to create file \"OutputFile\"")
	}
	defer tfh.file.Close()
	defer os.Remove("OutputFile")
	tcp.AddMessageHandler(tfh)

	// create file
	file, err := os.Create("InputFile")
	if err != nil {
		t.Fatal("Failed to create file \"InputFile\"")
	}
	defer file.Close()
	defer os.Remove("InputFile")
	err = ioutil.WriteFile("InputFile", bytes.Repeat([]byte("b"), 9001), 0644)
	if err != nil {
		t.Fatal("Failed to write to file InputFile")
	}

	// send file
	dest := common.Address{1, "localhost", 9988}
	err = tcp.SendFile(file, &dest)
	if err != nil {
		t.Fatal("Failed to send message:", err)
	}

	// wait for handler to be triggered
	<-tfh.done

	in, out := make([]byte, 9001), make([]byte, 9001)
	io.ReadFull(file, in)
	io.ReadFull(tfh.file, out)
	if bytes.Compare(in, out) != 0 {
		t.Fatal("Files do not match")
	}
}
<|endoftext|>


<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package vix

import (
	"fmt"
	"io/ioutil"
	"strings"

	"github.com/cloudescape/govmx"
)

// Bus type to use when attaching CD/DVD drives and disks.
type BusType string

// Disk controllers
const (
	IDE  BusType = "ide"
	SCSI BusType = "scsi"
	SATA BusType = "sata"
)

// Device Type
const (
	CDROM_IMAGE string = "cdrom-image"
	CDROM_RAW   string = "cdrom-raw"
)

// CD/DVD configuration
type CDDVDConfig struct {
	ID string
	// Either IDE, SCSI or SATA
	Bus BusType
	// Used only when attaching image files. Ex: ISO images
	// If you just want to attach a raw cdrom device leave it empty
	Filename string
}

// Attaches a CD/DVD drive to the virtual machine.
// TODO(c4milo): make it thread safe
// TODO(c4milo): Avoid unmarshaling in this function, it should be done somewhere
// in host.OpenVM just once.
func (v *VM) AttachCDDVD(config *CDDVDConfig) error {
	if running, _ := v.IsRunning(); running {
		return &VixError{
			Operation: "vm.AttachCDDVD",
			Code:      200000,
			Text:      "Virtual machine must be powered off in order to attach a CD/DVD drive.",
		}
	}

	v.vmxfile.Seek(0, 0)
	data, err := ioutil.ReadAll(v.vmxfile)
	if err != nil {
		return err
	}

	vm := new(vmx.VirtualMachine)

	err = vmx.Unmarshal(data, vm)
	if err != nil {
		return err
	}

	device := vmx.Device{}
	if config.Filename != "" {
		device.Filename = config.Filename
		device.Type = CDROM_IMAGE
	} else {
		device.Type = CDROM_RAW
		device.Autodetect = true
	}

	device.Present = true
	device.StartConnected = true

	switch config.Bus {
	case IDE:
		vm.IDEDevices = append(vm.IDEDevices, vmx.IDEDevice{device})
	case SCSI:
		vm.SCSIDevices = append(vm.SCSIDevices, vmx.SCSIDevice{device})
	case SATA:
		vm.SATADevices = append(vm.SATADevices, vmx.SATADevice{device})
	default:
		return &VixError{
			Operation: "vm.AttachCDDVD",
			Code:      200001,
			Text:      fmt.Sprintf("Unrecognized bus type: %s\n", config.Bus),
		}
	}

	newdata, err := vmx.Marshal(vm)
	if err != nil {
		return err
	}

	v.vmxfile.Seek(0, 0)
	_, err = v.vmxfile.Write(newdata)

	return err
}

// Detaches a CD/DVD device from the virtual machine
// TODO(c4milo): make it thread safe
// TODO(c4milo): Avoid unmarshaling in this function, it should be done somewhere
// in host.OpenVM just once.
func (v *VM) DetachCDDVD(config *CDDVDConfig) error {
	if running, _ := v.IsRunning(); running {
		return &VixError{
			Operation: "vm.DetachCDDVD",
			Code:      200002,
			Text:      "Virtual machine must be powered off in order to detach CD/DVD drive.",
		}
	}

	v.vmxfile.Seek(0, 0)
	data, err := ioutil.ReadAll(v.vmxfile)
	if err != nil {
		return err
	}

	vm := new(vmx.VirtualMachine)

	err = vmx.Unmarshal(data, vm)
	if err != nil {
		return err
	}

	switch config.Bus {
	case IDE:
		for i, device := range vm.IDEDevices {
			if config.ID == device.VMXID {
				// This method of removing the element avoids memory leaks
				copy(vm.IDEDevices[i:], vm.IDEDevices[i+1:])
				vm.IDEDevices[len(vm.IDEDevices)-1] = vmx.IDEDevice{}
				vm.IDEDevices = vm.IDEDevices[:len(vm.IDEDevices)-1]
			}
		}
	case SCSI:
		for i, device := range vm.SCSIDevices {
			if config.ID == device.VMXID {
				copy(vm.SCSIDevices[i:], vm.SCSIDevices[i+1:])
				vm.SCSIDevices[len(vm.SCSIDevices)-1] = vmx.SCSIDevice{}
				vm.SCSIDevices = vm.SCSIDevices[:len(vm.SCSIDevices)-1]
			}
		}
	case SATA:
		for i, device := range vm.SATADevices {
			if config.ID == device.VMXID {
				copy(vm.SATADevices[i:], vm.SATADevices[i+1:])
				vm.SATADevices[len(vm.SATADevices)-1] = vmx.SATADevice{}
				vm.SATADevices = vm.SATADevices[:len(vm.SATADevices)-1]
			}
		}
	default:
		return &VixError{
			Operation: "vm.DetachCDDVD",
			Code:      200003,
			Text:      fmt.Sprintf("Unrecognized bus type: %s\n", config.Bus),
		}
	}

	newdata, err := vmx.Marshal(vm)
	if err != nil {
		return err
	}

	v.vmxfile.Seek(0, 0)
	_, err = v.vmxfile.Write(newdata)

	return nil
}

// Returns an unordered slice of currently attached CD/DVD devices on any bus.
// TODO(c4milo): Avoid unmarshaling in this function, it should be done somewhere
// in host.OpenVM just once.
func (v *VM) CDDVDs() ([]*CDDVDConfig, error) {
	// unmarshal vmx
	v.vmxfile.Seek(0, 0)
	data, err := ioutil.ReadAll(v.vmxfile)
	if err != nil {
		return nil, fmt.Errorf("read file: %v", err)
	}

	vm := new(vmx.VirtualMachine)
	if err := vmx.Unmarshal(data, vm); err != nil {
		return nil, fmt.Errorf("unmarshal: %v", err)
	}

	var cddvds []*CDDVDConfig
	handle := func(d vmx.Device) {
		if d.Type == CDROM_IMAGE || d.Type == CDROM_RAW {
			cddvds = append(cddvds, &CDDVDConfig{
				ID:       d.VMXID,
				Bus:      IDE,
				Filename: d.Filename,
			})
		}
	}
	for _, d := range vm.IDEDevices {
		handle(d.Device)
	}
	for _, d := range vm.SCSIDevices {
		handle(d.Device)
	}
	for _, d := range vm.SATADevices {
		handle(d.Device)
	}
	return cddvds, nil
}

// Returns the CD/DVD drive identified by ID
// This function depends entirely on how GoVMX identifies array's elements
// TODO(c4milo): Avoid unmarshaling in this function, it should be done somewhere
// in host.OpenVM just once.
func (v *VM) CDDVD(ID string) (*CDDVDConfig, error) {
	// unmarshal vmx
	v.vmxfile.Seek(0, 0)
	data, err := ioutil.ReadAll(v.vmxfile)
	if err != nil {
		return nil, err
	}

	vm := new(vmx.VirtualMachine)

	err = vmx.Unmarshal(data, vm)
	if err != nil {
		return nil, err
	}

	cddvd := &CDDVDConfig{}
	if strings.HasPrefix(ID, string(IDE)) {
		for _, device := range vm.IDEDevices {
			if ID == device.VMXID {
				cddvd.Bus = IDE
				cddvd.Filename = device.Filename
				return cddvd, nil
			}
		}
	}

	if strings.HasPrefix(ID, string(SCSI)) {
		for _, device := range vm.SCSIDevices {
			if ID == device.VMXID {
				cddvd.Bus = SCSI
				cddvd.Filename = device.Filename
				return cddvd, nil
			}
		}
	}

	if strings.HasPrefix(ID, string(SATA)) {
		for _, device := range vm.SATADevices {
			if ID == device.VMXID {
				cddvd.Bus = SATA
				cddvd.Filename = device.Filename
				return cddvd, nil
			}
		}
	}

	return nil, nil
}
<commit_msg>more refactoring<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package vix

import (
	"fmt"
	"io/ioutil"
	"strings"

	"github.com/cloudescape/govmx"
)

// Bus type to use when attaching CD/DVD drives and disks.
type BusType string

// Disk controllers
const (
	IDE  BusType = "ide"
	SCSI BusType = "scsi"
	SATA BusType = "sata"
)

// Device Type
const (
	CDROM_IMAGE string = "cdrom-image"
	CDROM_RAW   string = "cdrom-raw"
)

// CD/DVD configuration
type CDDVDConfig struct {
	ID string
	// Either IDE, SCSI or SATA
	Bus BusType
	// Used only when attaching image files. Ex: ISO images
	// If you just want to attach a raw cdrom device leave it empty
	Filename string
}

// Attaches a CD/DVD drive to the virtual machine.
// TODO(c4milo): make it thread safe
// TODO(c4milo): Avoid unmarshaling in this function, it should be done somewhere
// in host.OpenVM just once.
func (v *VM) AttachCDDVD(config *CDDVDConfig) error {
	if running, _ := v.IsRunning(); running {
		return &VixError{
			Operation: "vm.AttachCDDVD",
			Code:      200000,
			Text:      "Virtual machine must be powered off in order to attach a CD/DVD drive.",
		}
	}

	v.vmxfile.Seek(0, 0)
	data, err := ioutil.ReadAll(v.vmxfile)
	if err != nil {
		return err
	}

	vm := new(vmx.VirtualMachine)

	err = vmx.Unmarshal(data, vm)
	if err != nil {
		return err
	}

	device := vmx.Device{}
	if config.Filename != "" {
		device.Filename = config.Filename
		device.Type = CDROM_IMAGE
	} else {
		device.Type = CDROM_RAW
		device.Autodetect = true
	}

	device.Present = true
	device.StartConnected = true

	switch config.Bus {
	case IDE:
		vm.IDEDevices = append(vm.IDEDevices, vmx.IDEDevice{device})
	case SCSI:
		vm.SCSIDevices = append(vm.SCSIDevices, vmx.SCSIDevice{device})
	case SATA:
		vm.SATADevices = append(vm.SATADevices, vmx.SATADevice{device})
	default:
		return &VixError{
			Operation: "vm.AttachCDDVD",
			Code:      200001,
			Text:      fmt.Sprintf("Unrecognized bus type: %s\n", config.Bus),
		}
	}

	newdata, err := vmx.Marshal(vm)
	if err != nil {
		return err
	}

	v.vmxfile.Seek(0, 0)
	_, err = v.vmxfile.Write(newdata)

	return err
}

// Detaches a CD/DVD device from the virtual machine
// TODO(c4milo): make it thread safe
// TODO(c4milo): Avoid unmarshaling in this function, it should be done somewhere
// in host.OpenVM just once.
func (v *VM) DetachCDDVD(config *CDDVDConfig) error {
	if running, _ := v.IsRunning(); running {
		return &VixError{
			Operation: "vm.DetachCDDVD",
			Code:      200002,
			Text:      "Virtual machine must be powered off in order to detach CD/DVD drive.",
		}
	}

	v.vmxfile.Seek(0, 0)
	data, err := ioutil.ReadAll(v.vmxfile)
	if err != nil {
		return err
	}

	vm := new(vmx.VirtualMachine)

	err = vmx.Unmarshal(data, vm)
	if err != nil {
		return err
	}

	switch config.Bus {
	case IDE:
		for i, device := range vm.IDEDevices {
			if config.ID == device.VMXID {
				// This method of removing the element avoids memory leaks
				copy(vm.IDEDevices[i:], vm.IDEDevices[i+1:])
				vm.IDEDevices[len(vm.IDEDevices)-1] = vmx.IDEDevice{}
				vm.IDEDevices = vm.IDEDevices[:len(vm.IDEDevices)-1]
			}
		}
	case SCSI:
		for i, device := range vm.SCSIDevices {
			if config.ID == device.VMXID {
				copy(vm.SCSIDevices[i:], vm.SCSIDevices[i+1:])
				vm.SCSIDevices[len(vm.SCSIDevices)-1] = vmx.SCSIDevice{}
				vm.SCSIDevices = vm.SCSIDevices[:len(vm.SCSIDevices)-1]
			}
		}
	case SATA:
		for i, device := range vm.SATADevices {
			if config.ID == device.VMXID {
				copy(vm.SATADevices[i:], vm.SATADevices[i+1:])
				vm.SATADevices[len(vm.SATADevices)-1] = vmx.SATADevice{}
				vm.SATADevices = vm.SATADevices[:len(vm.SATADevices)-1]
			}
		}
	default:
		return &VixError{
			Operation: "vm.DetachCDDVD",
			Code:      200003,
			Text:      fmt.Sprintf("Unrecognized bus type: %s\n", config.Bus),
		}
	}

	newdata, err := vmx.Marshal(vm)
	if err != nil {
		return err
	}

	v.vmxfile.Seek(0, 0)
	_, err = v.vmxfile.Write(newdata)

	return nil
}

// Returns an unordered slice of currently attached CD/DVD devices on any bus.
// TODO(c4milo): Avoid unmarshaling in this function, it should be done somewhere
// in host.OpenVM just once.
func (v *VM) CDDVDs() ([]*CDDVDConfig, error) {
	// unmarshal vmx
	v.vmxfile.Seek(0, 0)
	data, err := ioutil.ReadAll(v.vmxfile)
	if err != nil {
		return nil, fmt.Errorf("read file: %v", err)
	}

	vm := new(vmx.VirtualMachine)
	if err := vmx.Unmarshal(data, vm); err != nil {
		return nil, fmt.Errorf("unmarshal: %v", err)
	}

	var cddvds []*CDDVDConfig
	vm.WalkDevices(func(d vmx.Device) bool {
		if d.Type == CDROM_IMAGE || d.Type == CDROM_RAW {
			cddvds = append(cddvds, &CDDVDConfig{
				ID:       d.VMXID,
				Bus:      IDE,
				Filename: d.Filename,
			})
		}
		return false // keep going
	})
	return cddvds, nil
}

// Returns the CD/DVD drive identified by ID
// This function depends entirely on how GoVMX identifies array's elements
// TODO(c4milo): Avoid unmarshaling in this function, it should be done somewhere
// in host.OpenVM just once.
func (v *VM) CDDVD(ID string) (*CDDVDConfig, error) {
	// unmarshal vmx
	v.vmxfile.Seek(0, 0)
	data, err := ioutil.ReadAll(v.vmxfile)
	if err != nil {
		return nil, err
	}

	vm := new(vmx.VirtualMachine)

	err = vmx.Unmarshal(data, vm)
	if err != nil {
		return nil, err
	}

	var bus string
	switch {
	case strings.HasPrefix(ID, string(IDE)):
		bus = IDE
	case strings.HasPrefix(ID, string(SCSI)):
		bus = SCSI
	case strings.HasPrefix(ID, string(SATA)):
		bus = SATA
	}
	var filename string
	found := vm.WalkDevices(func(d vmx.Device) bool {
		if ID == d.VMXID {
			filename = d.Filename
			return true // stop walk
		}
		return false // keep going
	}, bus)

	if !found {
		return nil, nil
	}
	return &CDDVDConfig{Bus: bus, Filename: filename}, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"go/build"
	"os"
	"path/filepath"
	"strings"
)

type stringSet map[string]struct{}

func (ss stringSet) add(val string) { ss[val] = struct{}{} }
func (ss stringSet) has(val string) bool {
	_, ok := ss[val]
	return ok
}

// compute the set of dependencies for a list of packages. The packages must
// already be present in toolDirPath.
func dependencies(pkgs []string) stringSet {
	deps := stringSet{}

	buildCtx := build.Default
	buildCtx.GOPATH = toolDirPath

	var resolve func(string, []string)
	resolve = func(parent string, pkgs []string) {
		for _, pkg := range pkgs {
			if !strings.Contains(pkg, ".") {
				continue
			}

			p, err := buildCtx.Import(pkg, filepath.Join(toolDirPath, "src", parent), 0)
			if err != nil {
				fatal(fmt.Sprintf("couldn't import package %q", pkg), err)
			}

			if deps.has(p.ImportPath) {
				continue
			}

			deps.add(p.ImportPath)
			resolve(p.ImportPath, p.Imports)
		}
	}

	resolve("", pkgs)

	return deps
}

// Remove unused files and unused packages from toolDirPath.
func clean(pkgs []string) {
	deps := dependencies(pkgs)
	base := filepath.Join(toolDirPath, "src")

	// Resolve any symlinks in the packages to keep, because we're going
	// to walk through the file system, so we need to trim stuff by
	// _filename_.
	for pkgPath := range deps {
		fullPath := filepath.Join(base, pkgPath)
		resolved, err := filepath.EvalSymlinks(fullPath)
		if err != nil {
			fatal(fmt.Sprintf("couldn't eval symlinks in %q", pkgPath), err)
		}
		// Undo the filepath.Join from above
		pkgPath, err = filepath.Rel(base, resolved)
		if err != nil {
			fatal(fmt.Sprintf("couldn't eval symlinks in %q", pkgPath), err)
		}
		deps.add(pkgPath)
	}

	var toDelete []string
	err := filepath.Walk(base, func(path string, info os.FileInfo, err error) error {
		// Bubble up errors
		if err != nil {
			return err
		}

		// Skip the root directory
		if base == path {
			return nil
		}

		// Get the package directory
		pkg, err := filepath.Rel(base, path)
		if err != nil {
			return err
		}

		// Delete files in packages that aren't in packages we need to build the
		// tools, and any non-go, non-legal files in packages we *do* need.
		if info.Mode().IsRegular() {
			pkg = filepath.Dir(pkg)
			if !(deps.has(pkg) && keepFile(path)) {
				toDelete = append(toDelete, path)
			}
			return nil
		}

		// If the path is a directory that's specially marked for preservation, keep
		// it and all its contents.
		if info.IsDir() && preserveDirectory(path) {
			return filepath.SkipDir
		}

		// If the folder is a kept package or a parent, don't delete it and keep recursing
		for p := range deps {
			if strings.HasPrefix(p, pkg) {
				return nil
			}
		}

		// Otherwise this is a package that isn't imported at all. Delete it and stop recursing
		toDelete = append(toDelete, path)
		return filepath.SkipDir
	})

	if err != nil {
		fatal("unable to clean _tools", err)
	}

	for _, path := range toDelete {
		err = os.RemoveAll(path)
		if err != nil {
			fatal("unable to remove file or directory", err)
		}
	}
}

func keepFile(filename string) bool {
	if strings.HasSuffix(filename, "_test.go") {
		return false
	}

	switch filepath.Ext(filename) {
	case ".go", ".s", ".c", ".h":
		return true
	}

	if isLegalFile(filename) {
		return true
	}
	return false
}

var commonLegalFilePrefixes = []string{
	"licence", // UK spelling
	"license", // US spelling
	"copying",
	"unlicense",
	"copyright",
	"copyleft",
	"authors",
	"contributors",
	"readme", // often has a license inline
}

var commonLegalFileSubstrings = []string{
	"legal",
	"notice",
	"disclaimer",
	"patent",
	"third-party",
	"thirdparty",
}

func isLegalFile(filename string) bool {
	base := strings.ToLower(filepath.Base(filename))
	for _, p := range commonLegalFilePrefixes {
		if strings.HasPrefix(base, p) {
			return true
		}
	}
	for _, s := range commonLegalFileSubstrings {
		if strings.Contains(base, s) {
			return true
		}
	}
	return false
}

// List of directories that should be completely preserved if they are present.
var preservedDirectories = []string{
	// gometalinter vendors its own linters and relies on this directory's
	// existence. See issue #7.
	filepath.Join("github.com", "alecthomas", "gometalinter", "_linters"),

	// sqlboiler requires template files to exist at runtime. See pull request #25.
	filepath.Join("github.com", "vattle", "sqlboiler", "templates"),
	filepath.Join("github.com", "vattle", "sqlboiler", "templates_test"),
}

// checks whether path is in the list of preserved directories.
func preserveDirectory(path string) bool {
	for _, d := range preservedDirectories {
		if strings.HasSuffix(path, d) {
			return true
		}
	}
	return false
}
<commit_msg>Handle renamed sqlboiler (#34)<commit_after>package main

import (
	"fmt"
	"go/build"
	"os"
	"path/filepath"
	"strings"
)

type stringSet map[string]struct{}

func (ss stringSet) add(val string) { ss[val] = struct{}{} }
func (ss stringSet) has(val string) bool {
	_, ok := ss[val]
	return ok
}

// compute the set of dependencies for a list of packages. The packages must
// already be present in toolDirPath.
func dependencies(pkgs []string) stringSet {
	deps := stringSet{}

	buildCtx := build.Default
	buildCtx.GOPATH = toolDirPath

	var resolve func(string, []string)
	resolve = func(parent string, pkgs []string) {
		for _, pkg := range pkgs {
			if !strings.Contains(pkg, ".") {
				continue
			}

			p, err := buildCtx.Import(pkg, filepath.Join(toolDirPath, "src", parent), 0)
			if err != nil {
				fatal(fmt.Sprintf("couldn't import package %q", pkg), err)
			}

			if deps.has(p.ImportPath) {
				continue
			}

			deps.add(p.ImportPath)
			resolve(p.ImportPath, p.Imports)
		}
	}

	resolve("", pkgs)

	return deps
}

// Remove unused files and unused packages from toolDirPath.
func clean(pkgs []string) {
	deps := dependencies(pkgs)
	base := filepath.Join(toolDirPath, "src")

	// Resolve any symlinks in the packages to keep, because we're going
	// to walk through the file system, so we need to trim stuff by
	// _filename_.
	for pkgPath := range deps {
		fullPath := filepath.Join(base, pkgPath)
		resolved, err := filepath.EvalSymlinks(fullPath)
		if err != nil {
			fatal(fmt.Sprintf("couldn't eval symlinks in %q", pkgPath), err)
		}
		// Undo the filepath.Join from above
		pkgPath, err = filepath.Rel(base, resolved)
		if err != nil {
			fatal(fmt.Sprintf("couldn't eval symlinks in %q", pkgPath), err)
		}
		deps.add(pkgPath)
	}

	var toDelete []string
	err := filepath.Walk(base, func(path string, info os.FileInfo, err error) error {
		// Bubble up errors
		if err != nil {
			return err
		}

		// Skip the root directory
		if base == path {
			return nil
		}

		// Get the package directory
		pkg, err := filepath.Rel(base, path)
		if err != nil {
			return err
		}

		// Delete files in packages that aren't in packages we need to build the
		// tools, and any non-go, non-legal files in packages we *do* need.
		if info.Mode().IsRegular() {
			pkg = filepath.Dir(pkg)
			if !(deps.has(pkg) && keepFile(path)) {
				toDelete = append(toDelete, path)
			}
			return nil
		}

		// If the path is a directory that's specially marked for preservation, keep
		// it and all its contents.
		if info.IsDir() && preserveDirectory(path) {
			return filepath.SkipDir
		}

		// If the folder is a kept package or a parent, don't delete it and keep recursing
		for p := range deps {
			if strings.HasPrefix(p, pkg) {
				return nil
			}
		}

		// Otherwise this is a package that isn't imported at all. Delete it and stop recursing
		toDelete = append(toDelete, path)
		return filepath.SkipDir
	})

	if err != nil {
		fatal("unable to clean _tools", err)
	}

	for _, path := range toDelete {
		err = os.RemoveAll(path)
		if err != nil {
			fatal("unable to remove file or directory", err)
		}
	}
}

func keepFile(filename string) bool {
	if strings.HasSuffix(filename, "_test.go") {
		return false
	}

	switch filepath.Ext(filename) {
	case ".go", ".s", ".c", ".h":
		return true
	}

	if isLegalFile(filename) {
		return true
	}
	return false
}

var commonLegalFilePrefixes = []string{
	"licence", // UK spelling
	"license", // US spelling
	"copying",
	"unlicense",
	"copyright",
	"copyleft",
	"authors",
	"contributors",
	"readme", // often has a license inline
}

var commonLegalFileSubstrings = []string{
	"legal",
	"notice",
	"disclaimer",
	"patent",
	"third-party",
	"thirdparty",
}

func isLegalFile(filename string) bool {
	base := strings.ToLower(filepath.Base(filename))
	for _, p := range commonLegalFilePrefixes {
		if strings.HasPrefix(base, p) {
			return true
		}
	}
	for _, s := range commonLegalFileSubstrings {
		if strings.Contains(base, s) {
			return true
		}
	}
	return false
}

// List of directories that should be completely preserved if they are present.
var preservedDirectories = []string{
	// gometalinter vendors its own linters and relies on this directory's
	// existence. See issue #7.
	filepath.Join("github.com", "alecthomas", "gometalinter", "_linters"),

	// sqlboiler requires template files to exist at runtime. See pull request #25.
	filepath.Join("github.com", "vattle", "sqlboiler", "templates"),
	filepath.Join("github.com", "vattle", "sqlboiler", "templates_test"),
	filepath.Join("github.com", "volatiletech", "sqlboiler", "templates"),
	filepath.Join("github.com", "volatiletech", "sqlboiler", "templates_test"),
}

// checks whether path is in the list of preserved directories.
func preserveDirectory(path string) bool {
	for _, d := range preservedDirectories {
		if strings.HasSuffix(path, d) {
			return true
		}
	}
	return false
}
<|endoftext|>