<commit_before>// Copyright 2016 henrylee2cn.  All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package core

import (
	"path"
	"path/filepath"
	"reflect"
	"regexp"
	"runtime"
	"strings"
	"sync"
)

type (
	// 应用模块
	Module struct {
		Name        string
		Class       string
		Description string
		Id          string
		status      int
		sync.Mutex
		*Themes
		*Group
	}
	// 登记模块列表
	Modules struct {
		// 快速调用列表
		Map map[string]*Module
		// 有序列表 [分组][Id]*Module
		Slice [][]*Module
	}
)

var (
	re = regexp.MustCompile("^[/]?([a-zA-Z0-9_]+)([\\./\\?])?")
)

func newModules() *Modules {
	return &Modules{
		Map:   map[string]*Module{},
		Slice: [][]*Module{},
	}
}

// 初始化模块，文件名作为id，且文件名应与模块目录名、包名保存一致
func ModulePrepare(m *Module) *Module {
	_, file, _, _ := runtime.Caller(1)
	name := strings.TrimSuffix(filepath.Base(file), ".go")
	if name == "home" {
		m.Id = "/"
	} else {
		m.Id = "/" + name
	}

	// 创建分组并修改请求路径c.path "/[模块]/[控制器]/[操作]"为"/[模块]/[主题]/[控制器]/[操作]"
	m.Group = ThinkGo.Echo.Group("/"+m.Id, func(c *Context) error {
		p := strings.Split(c.Path(), "/:")[0]
		if p == "/" || p == "" {
			c.SetPath("/index/index")
		} else if strings.HasSuffix(p, "/index") {
			l := 3
			if c.Echo().Prefix() == "/" {
				l = 2
			}
			num := l - strings.Count(p, "/")
			if num > 0 {
				c.SetPath(p + strings.Repeat("/index", num))
			}
		}
		p = path.Join(m.Id, m.Themes.Cur, strings.TrimPrefix(p, m.Id))
		// 插入主题字段
		c.SetPath(p)
		// 静态文件前缀
		c.Set("__PUBLIC__", path.Join(PUBLIC_PREFIX, m.Id, m.Themes.Cur))
		return nil
	})

	// 登记并排序
	insertModule(m)
	return m
}

// 获取Id
func (this *Module) GetId() string {
	return this.Id
}

// 设置主题，自动设置传入的第1个主题为当前主题
func (this *Module) SetThemes(themes ...*Theme) *Module {
	if len(themes) == 0 {
		return this
	}
	this.Themes = &Themes{
		Cur:  themes[0].Name,
		List: make(map[string]*Theme),
	}
	this.Themes.AddThemes(themes...)
	return this
}

// 设置当前主题
func (this *Module) UseTheme(name string) *Module {
	this.Themes.UseTheme(name)
	return this
}

// 定义中间件
func (this *Module) Use(m ...Middleware) *Module {
	this.Group.Use(m...)
	return this
}

// @pattern: 方法名[/参数规则]
func (this *Module) CONNECT(pattern string, c Controller) *Module {
	this.router(CONNECT, pattern, c)
	return this
}

// @pattern: 方法名[/参数规则]
func (this *Module) DELETE(pattern string, c Controller) *Module {
	this.router(DELETE, pattern, c)
	return this
}

// @pattern: 方法名[/参数规则]
func (this *Module) GET(pattern string, c Controller) *Module {
	this.router(GET, pattern, c)
	return this
}

// @pattern: 方法名[/参数规则]
func (this *Module) HEAD(pattern string, c Controller) *Module {
	this.router(HEAD, pattern, c)
	return this
}

// @pattern: 方法名[/参数规则]
func (this *Module) OPTIONS(pattern string, c Controller) *Module {
	this.router(OPTIONS, pattern, c)
	return this
}

// @pattern: 方法名[/参数规则]
func (this *Module) PATCH(pattern string, c Controller) *Module {
	this.router(PATCH, pattern, c)
	return this
}

// @pattern: 方法名[/参数规则]
func (this *Module) POST(pattern string, c Controller) *Module {
	this.router(POST, pattern, c)
	return this
}

// @pattern: 方法名[/参数规则]
func (this *Module) PUT(pattern string, c Controller) *Module {
	this.router(PUT, pattern, c)
	return this
}

// @pattern: 方法名[/参数规则]
func (this *Module) TRACE(pattern string, c Controller) *Module {
	this.router(TRACE, pattern, c)
	return this
}

// @pattern: 方法名[/参数规则]
func (this *Module) SOCKET(pattern string, c Controller) *Module {
	this.router(SOCKET, pattern, c)
	return this
}

// 公用路由注册方法
// 注：路由规则"/ReadMail/:id" 将被自动转为 "/read_mail/:id"
// 注：路由规则"read" 将被自动转为 "/read"
// 注：当为"home"模块时，同时在根目录注册路由
func (this *Module) router(method, pattern string, c Controller) {
	this.Mutex.Lock()
	defer func() {
		recover()
		this.Mutex.Unlock()
	}()
	pattern, a := dealPattern(pattern)

	cname, fname, h := this.newHandler(CamelString(a[1]), c)

	echo := this.Group.Echo()
	prefix := echo.Prefix()
	cname = "/" + cname
	if method != SOCKET {
		add := echo.Match
		if prefix == "/home" {
			// 当为"home"模块时，添加注册根路由
			add = ThinkGo.Echo.Match
		}

		// 允许省略index
		if fname == "index" && a[2] != "/" {
			p := path.Join(cname, pattern[len(a[1])+1:])
			// p = strings.Replace(p, "/?", "?", -1)
			add([]string{method}, p, h)
			if cname == "/index" {
				add([]string{method}, "/"+pattern[len(a[1])+1:], h)
			}
		}
		add([]string{method}, path.Join(cname, pattern), h)

	} else {
		add := echo.WebSocket
		if prefix == "/home" {
			// 当为"home"模块时，添加注册根路由
			add = ThinkGo.Echo.WebSocket
		}

		// 允许省略index
		if fname == "index" && a[2] != "/" {
			p := path.Join(cname, pattern[len(a[1]):])
			// p = strings.Replace(p, "/?", "?", -1)
			add(p, h)
			if cname == "/index" {
				add("/"+pattern[len(a[1]):], h)
			}
		}
		add(path.Join(cname, pattern), h)
	}
}

// 返回闭包操作
func (this *Module) newHandler(fname string, c Controller) (_cname, _fname string, h HandlerFunc) {
	t := reflect.TypeOf(c)
	has := false
	for i := t.NumMethod() - 1; i >= 0; i-- {
		mname := t.Method(i).Name
		if strings.EqualFold(mname, fname) {
			fname = mname
			has = true
			break
		}
	}
	t = t.Elem()
	if !has {
		ThinkGo.Logger().Fatal(`[ERROR]  配置路由规则: 指定方法名 "` + fname + `" 在控制器 [` + t.Name() + `] 不存在(忽略大小写)`)
	}

	_cname = SnakeString(strings.TrimSuffix(t.Name(), "Controller"))
	_fname = SnakeString(fname)
	h = func(ctx *Context) error {
		var newv = reflect.New(t)
		newv.Interface().(Controller).AutoInit(ctx, this)
		vs := newv.MethodByName(fname).Call([]reflect.Value{})
		if len(vs) > 0 {
			if err, ok := vs[0].Interface().(error); ok {
				return err
			}
		}
		return nil
	}
	return
}

func dealPattern(s string) (string, []string) {
	s = strings.Trim(s, "/")
	s = strings.Trim(s, "?")
	// s = strings.Replace(s, "/?", "?", -1)
	s = strings.Split(s, "?")[0]
	a := re.FindStringSubmatch(s)
	s = "/" + SnakeString(s)
	if len(a) < 3 {
		ThinkGo.Logger().Fatal(`[ERROR]  配置路由规则: 匹配规则 "` + s + `" 不正确`)
	}
	return s, a
}

// 顺序插入插件
func insertModule(m *Module) {
	// 添加至插件索引列表
	ThinkGo.Modules.Map[m.Id] = m

	// 添加至插件有序列表
	var (
		add   bool
		class []string
	)

	for _, ms := range ThinkGo.Modules.Slice {
		c := ms[0].Class
		class = append(class, c)
		if c != m.Class {
			continue
		}
		for k, v := range ms {
			if v.Name > m.Name {
				x := append([]*Module{m}, ms[k:]...)
				ms = append(ms[:k], x...)
				break
			}
		}
		add = true
		break
	}
	if add {
		return
	}

	if len(class) == 0 {
		ThinkGo.Modules.Slice = append(ThinkGo.Modules.Slice, []*Module{m})
		return
	}

	for k, v := range class {
		if v > m.Class {
			x := append([][]*Module{{m}}, ThinkGo.Modules.Slice[k:]...)
			ThinkGo.Modules.Slice = append(ThinkGo.Modules.Slice[:k], x...)
			break
		}
	}
}

func GetMoudleSlice() [][]*Module {
	return ThinkGo.Modules.Slice
}
<commit_msg>update<commit_after>// Copyright 2016 henrylee2cn.  All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package core

import (
	"path"
	"path/filepath"
	"reflect"
	"regexp"
	"runtime"
	"strings"
	"sync"
)

type (
	// 应用模块
	Module struct {
		Name        string
		Class       string
		Description string
		Id          string
		status      int
		sync.Mutex
		*Themes
		*Group
	}
	// 登记模块列表
	Modules struct {
		// 快速调用列表
		Map map[string]*Module
		// 有序列表 [分组][Id]*Module
		Slice [][]*Module
	}
)

var (
	re = regexp.MustCompile("^[/]?([a-zA-Z0-9_]+)([\\./\\?])?")
)

func newModules() *Modules {
	return &Modules{
		Map:   map[string]*Module{},
		Slice: [][]*Module{},
	}
}

// 初始化模块，文件名作为id，且文件名应与模块目录名、包名保存一致
func ModulePrepare(m *Module) *Module {
	_, file, _, _ := runtime.Caller(1)
	m.Id = strings.TrimSuffix(filepath.Base(file), ".go")
	prefix := "/" + m.Id
	if m.Id == "home" {
		prefix = ""
	}
	// 创建分组并修改请求路径c.path "/[模块]/[控制器]/[操作]"为"/[模块]/[主题]/[控制器]/[操作]"
	m.Group = ThinkGo.Echo.Group(prefix, func(c *Context) error {
		p := strings.Split(c.Path(), "/:")[0]
		if p == "/" || p == "" {
			c.SetPath("/index/index")
		} else if strings.HasSuffix(p, "/index") {
			l := 3
			if c.Echo().Prefix() == "/" {
				l = 2
			}
			num := l - strings.Count(p, "/")
			if num > 0 {
				c.SetPath(p + strings.Repeat("/index", num))
			}
		}
		p = path.Join(prefix, m.Themes.Cur, strings.TrimPrefix(p, prefix))
		// 插入主题字段
		c.SetPath(p)
		// 静态文件前缀
		c.Set("__PUBLIC__", path.Join(PUBLIC_PREFIX, prefix, m.Themes.Cur))
		return nil
	})

	// 登记并排序
	insertModule(m)
	return m
}

// 获取Id
func (this *Module) GetId() string {
	return this.Id
}

// 设置主题，自动设置传入的第1个主题为当前主题
func (this *Module) SetThemes(themes ...*Theme) *Module {
	if len(themes) == 0 {
		return this
	}
	this.Themes = &Themes{
		Cur:  themes[0].Name,
		List: make(map[string]*Theme),
	}
	this.Themes.AddThemes(themes...)
	return this
}

// 设置当前主题
func (this *Module) UseTheme(name string) *Module {
	this.Themes.UseTheme(name)
	return this
}

// 定义中间件
func (this *Module) Use(m ...Middleware) *Module {
	this.Group.Use(m...)
	return this
}

// @pattern: 方法名[/参数规则]
func (this *Module) CONNECT(pattern string, c Controller) *Module {
	this.router(CONNECT, pattern, c)
	return this
}

// @pattern: 方法名[/参数规则]
func (this *Module) DELETE(pattern string, c Controller) *Module {
	this.router(DELETE, pattern, c)
	return this
}

// @pattern: 方法名[/参数规则]
func (this *Module) GET(pattern string, c Controller) *Module {
	this.router(GET, pattern, c)
	return this
}

// @pattern: 方法名[/参数规则]
func (this *Module) HEAD(pattern string, c Controller) *Module {
	this.router(HEAD, pattern, c)
	return this
}

// @pattern: 方法名[/参数规则]
func (this *Module) OPTIONS(pattern string, c Controller) *Module {
	this.router(OPTIONS, pattern, c)
	return this
}

// @pattern: 方法名[/参数规则]
func (this *Module) PATCH(pattern string, c Controller) *Module {
	this.router(PATCH, pattern, c)
	return this
}

// @pattern: 方法名[/参数规则]
func (this *Module) POST(pattern string, c Controller) *Module {
	this.router(POST, pattern, c)
	return this
}

// @pattern: 方法名[/参数规则]
func (this *Module) PUT(pattern string, c Controller) *Module {
	this.router(PUT, pattern, c)
	return this
}

// @pattern: 方法名[/参数规则]
func (this *Module) TRACE(pattern string, c Controller) *Module {
	this.router(TRACE, pattern, c)
	return this
}

// @pattern: 方法名[/参数规则]
func (this *Module) SOCKET(pattern string, c Controller) *Module {
	this.router(SOCKET, pattern, c)
	return this
}

// 公用路由注册方法
// 注：路由规则"/ReadMail/:id" 将被自动转为 "/read_mail/:id"
// 注：路由规则"read" 将被自动转为 "/read"
// 注：当为"home"模块时，同时在根目录注册路由
func (this *Module) router(method, pattern string, c Controller) {
	this.Mutex.Lock()
	defer func() {
		recover()
		this.Mutex.Unlock()
	}()
	pattern, a := dealPattern(pattern)

	cname, fname, h := this.newHandler(CamelString(a[1]), c)

	echo := this.Group.Echo()
	prefix := echo.Prefix()
	cname = "/" + cname
	if method != SOCKET {
		add := echo.Match
		if prefix == "/home" {
			// 当为"home"模块时，添加注册根路由
			add = ThinkGo.Echo.Match
		}

		// 允许省略index
		if fname == "index" && a[2] != "/" {
			p := path.Join(cname, pattern[len(a[1])+1:])
			// p = strings.Replace(p, "/?", "?", -1)
			add([]string{method}, p, h)
			if cname == "/index" {
				add([]string{method}, "/"+pattern[len(a[1])+1:], h)
			}
		}
		add([]string{method}, path.Join(cname, pattern), h)

	} else {
		add := echo.WebSocket
		if prefix == "/home" {
			// 当为"home"模块时，添加注册根路由
			add = ThinkGo.Echo.WebSocket
		}

		// 允许省略index
		if fname == "index" && a[2] != "/" {
			p := path.Join(cname, pattern[len(a[1]):])
			// p = strings.Replace(p, "/?", "?", -1)
			add(p, h)
			if cname == "/index" {
				add("/"+pattern[len(a[1]):], h)
			}
		}
		add(path.Join(cname, pattern), h)
	}
}

// 返回闭包操作
func (this *Module) newHandler(fname string, c Controller) (_cname, _fname string, h HandlerFunc) {
	t := reflect.TypeOf(c)
	has := false
	for i := t.NumMethod() - 1; i >= 0; i-- {
		mname := t.Method(i).Name
		if strings.EqualFold(mname, fname) {
			fname = mname
			has = true
			break
		}
	}
	t = t.Elem()
	if !has {
		ThinkGo.Logger().Fatal(`[ERROR]  配置路由规则: 指定方法名 "` + fname + `" 在控制器 [` + t.Name() + `] 不存在(忽略大小写)`)
	}

	_cname = SnakeString(strings.TrimSuffix(t.Name(), "Controller"))
	_fname = SnakeString(fname)
	h = func(ctx *Context) error {
		var newv = reflect.New(t)
		newv.Interface().(Controller).AutoInit(ctx, this)
		vs := newv.MethodByName(fname).Call([]reflect.Value{})
		if len(vs) > 0 {
			if err, ok := vs[0].Interface().(error); ok {
				return err
			}
		}
		return nil
	}
	return
}

func dealPattern(s string) (string, []string) {
	s = strings.Trim(s, "/")
	s = strings.Trim(s, "?")
	// s = strings.Replace(s, "/?", "?", -1)
	s = strings.Split(s, "?")[0]
	a := re.FindStringSubmatch(s)
	s = "/" + SnakeString(s)
	if len(a) < 3 {
		ThinkGo.Logger().Fatal(`[ERROR]  配置路由规则: 匹配规则 "` + s + `" 不正确`)
	}
	return s, a
}

// 顺序插入插件
func insertModule(m *Module) {
	// 添加至插件索引列表
	ThinkGo.Modules.Map[m.Id] = m

	// 添加至插件有序列表
	var (
		add   bool
		class []string
	)

	for _, ms := range ThinkGo.Modules.Slice {
		c := ms[0].Class
		class = append(class, c)
		if c != m.Class {
			continue
		}
		for k, v := range ms {
			if v.Name > m.Name {
				x := append([]*Module{m}, ms[k:]...)
				ms = append(ms[:k], x...)
				break
			}
		}
		add = true
		break
	}
	if add {
		return
	}

	if len(class) == 0 {
		ThinkGo.Modules.Slice = append(ThinkGo.Modules.Slice, []*Module{m})
		return
	}

	for k, v := range class {
		if v > m.Class {
			x := append([][]*Module{{m}}, ThinkGo.Modules.Slice[k:]...)
			ThinkGo.Modules.Slice = append(ThinkGo.Modules.Slice[:k], x...)
			break
		}
	}
}

func GetMoudleSlice() [][]*Module {
	return ThinkGo.Modules.Slice
}
<|endoftext|>


<commit_before>package vm

import (
	"fmt"
	"math/big"

	"github.com/ethereum/go-ethereum/params"
)

var (
	GasQuickStep   = big.NewInt(2)
	GasFastestStep = big.NewInt(3)
	GasFastStep    = big.NewInt(5)
	GasMidStep     = big.NewInt(8)
	GasSlowStep    = big.NewInt(10)
	GasExtStep     = big.NewInt(20)

	GasReturn = big.NewInt(0)
	GasStop   = big.NewInt(0)

	GasContractByte = big.NewInt(200)
)

func baseCheck(op OpCode, stack *stack, gas *big.Int) error {
	// PUSH and DUP are a bit special. They all cost the same but we do want to have checking on stack push limit
	// PUSH is also allowed to calculate the same price for all PUSHes
	// DUP requirements are handled elsewhere (except for the stack limit check)
	if op >= PUSH1 && op <= PUSH32 {
		op = PUSH1
	}
	if op >= DUP1 && op <= DUP16 {
		op = DUP1
	}

	if r, ok := _baseCheck[op]; ok {
		err := stack.require(r.stackPop)
		if err != nil {
			return err
		}

		if r.stackPush && len(stack.data)-r.stackPop > int(params.StackLimit.Int64()) {
			return fmt.Errorf("stack limit reached %d (%d)", len(stack.data), params.StackLimit.Int64())
		}

		gas.Add(gas, r.gas)
	}
	return nil
}

func toWordSize(size *big.Int) *big.Int {
	tmp := new(big.Int)
	tmp.Add(size, u256(31))
	tmp.Div(tmp, u256(32))
	return tmp
}

type req struct {
	stackPop  int
	gas       *big.Int
	stackPush bool
}

var _baseCheck = map[OpCode]req{
	// opcode  |  stack pop | gas price | stack push
	ADD:          {2, GasFastestStep, true},
	LT:           {2, GasFastestStep, true},
	GT:           {2, GasFastestStep, true},
	SLT:          {2, GasFastestStep, true},
	SGT:          {2, GasFastestStep, true},
	EQ:           {2, GasFastestStep, true},
	ISZERO:       {1, GasFastestStep, true},
	SUB:          {2, GasFastestStep, true},
	AND:          {2, GasFastestStep, true},
	OR:           {2, GasFastestStep, true},
	XOR:          {2, GasFastestStep, true},
	NOT:          {1, GasFastestStep, true},
	BYTE:         {2, GasFastestStep, true},
	CALLDATALOAD: {1, GasFastestStep, true},
	CALLDATACOPY: {3, GasFastestStep, true},
	MLOAD:        {1, GasFastestStep, true},
	MSTORE:       {2, GasFastestStep, false},
	MSTORE8:      {2, GasFastestStep, false},
	CODECOPY:     {3, GasFastestStep, false},
	MUL:          {2, GasFastStep, true},
	DIV:          {2, GasFastStep, true},
	SDIV:         {2, GasFastStep, true},
	MOD:          {2, GasFastStep, true},
	SMOD:         {2, GasFastStep, true},
	SIGNEXTEND:   {2, GasFastStep, true},
	ADDMOD:       {3, GasMidStep, true},
	MULMOD:       {3, GasMidStep, true},
	JUMP:         {1, GasMidStep, false},
	JUMPI:        {2, GasSlowStep, false},
	EXP:          {2, GasSlowStep, true},
	ADDRESS:      {0, GasQuickStep, true},
	ORIGIN:       {0, GasQuickStep, true},
	CALLER:       {0, GasQuickStep, true},
	CALLVALUE:    {0, GasQuickStep, true},
	CODESIZE:     {0, GasQuickStep, true},
	GASPRICE:     {0, GasQuickStep, true},
	COINBASE:     {0, GasQuickStep, true},
	TIMESTAMP:    {0, GasQuickStep, true},
	NUMBER:       {0, GasQuickStep, true},
	CALLDATASIZE: {0, GasQuickStep, true},
	DIFFICULTY:   {0, GasQuickStep, true},
	GASLIMIT:     {0, GasQuickStep, true},
	POP:          {1, GasQuickStep, false},
	PC:           {0, GasQuickStep, true},
	MSIZE:        {0, GasQuickStep, true},
	GAS:          {0, GasQuickStep, true},
	BLOCKHASH:    {1, GasExtStep, true},
	BALANCE:      {1, GasExtStep, true},
	EXTCODESIZE:  {1, GasExtStep, true},
	EXTCODECOPY:  {4, GasExtStep, false},
	SLOAD:        {1, params.SloadGas, true},
	SSTORE:       {2, Zero, false},
	SHA3:         {2, params.Sha3Gas, true},
	CREATE:       {3, params.CreateGas, true},
	CALL:         {7, params.CallGas, true},
	CALLCODE:     {7, params.CallGas, true},
	JUMPDEST:     {0, params.JumpdestGas, false},
	SUICIDE:      {1, Zero, false},
	RETURN:       {2, Zero, false},
	PUSH1:        {0, GasFastestStep, true},
	DUP1:         {0, Zero, true},
}
<commit_msg>bool => int<commit_after>package vm

import (
	"fmt"
	"math/big"

	"github.com/ethereum/go-ethereum/params"
)

var (
	GasQuickStep   = big.NewInt(2)
	GasFastestStep = big.NewInt(3)
	GasFastStep    = big.NewInt(5)
	GasMidStep     = big.NewInt(8)
	GasSlowStep    = big.NewInt(10)
	GasExtStep     = big.NewInt(20)

	GasReturn = big.NewInt(0)
	GasStop   = big.NewInt(0)

	GasContractByte = big.NewInt(200)
)

func baseCheck(op OpCode, stack *stack, gas *big.Int) error {
	// PUSH and DUP are a bit special. They all cost the same but we do want to have checking on stack push limit
	// PUSH is also allowed to calculate the same price for all PUSHes
	// DUP requirements are handled elsewhere (except for the stack limit check)
	if op >= PUSH1 && op <= PUSH32 {
		op = PUSH1
	}
	if op >= DUP1 && op <= DUP16 {
		op = DUP1
	}

	if r, ok := _baseCheck[op]; ok {
		err := stack.require(r.stackPop)
		if err != nil {
			return err
		}

		if r.stackPush > 0 && len(stack.data)-r.stackPop+r.stackPush > int(params.StackLimit.Int64())+1 {
			return fmt.Errorf("stack limit reached %d (%d)", len(stack.data), params.StackLimit.Int64())
		}

		gas.Add(gas, r.gas)
	}
	return nil
}

func toWordSize(size *big.Int) *big.Int {
	tmp := new(big.Int)
	tmp.Add(size, u256(31))
	tmp.Div(tmp, u256(32))
	return tmp
}

type req struct {
	stackPop  int
	gas       *big.Int
	stackPush int
}

var _baseCheck = map[OpCode]req{
	// opcode  |  stack pop | gas price | stack push
	ADD:          {2, GasFastestStep, 1},
	LT:           {2, GasFastestStep, 1},
	GT:           {2, GasFastestStep, 1},
	SLT:          {2, GasFastestStep, 1},
	SGT:          {2, GasFastestStep, 1},
	EQ:           {2, GasFastestStep, 1},
	ISZERO:       {1, GasFastestStep, 1},
	SUB:          {2, GasFastestStep, 1},
	AND:          {2, GasFastestStep, 1},
	OR:           {2, GasFastestStep, 1},
	XOR:          {2, GasFastestStep, 1},
	NOT:          {1, GasFastestStep, 1},
	BYTE:         {2, GasFastestStep, 1},
	CALLDATALOAD: {1, GasFastestStep, 1},
	CALLDATACOPY: {3, GasFastestStep, 1},
	MLOAD:        {1, GasFastestStep, 1},
	MSTORE:       {2, GasFastestStep, 0},
	MSTORE8:      {2, GasFastestStep, 0},
	CODECOPY:     {3, GasFastestStep, 0},
	MUL:          {2, GasFastStep, 1},
	DIV:          {2, GasFastStep, 1},
	SDIV:         {2, GasFastStep, 1},
	MOD:          {2, GasFastStep, 1},
	SMOD:         {2, GasFastStep, 1},
	SIGNEXTEND:   {2, GasFastStep, 1},
	ADDMOD:       {3, GasMidStep, 1},
	MULMOD:       {3, GasMidStep, 1},
	JUMP:         {1, GasMidStep, 0},
	JUMPI:        {2, GasSlowStep, 0},
	EXP:          {2, GasSlowStep, 1},
	ADDRESS:      {0, GasQuickStep, 1},
	ORIGIN:       {0, GasQuickStep, 1},
	CALLER:       {0, GasQuickStep, 1},
	CALLVALUE:    {0, GasQuickStep, 1},
	CODESIZE:     {0, GasQuickStep, 1},
	GASPRICE:     {0, GasQuickStep, 1},
	COINBASE:     {0, GasQuickStep, 1},
	TIMESTAMP:    {0, GasQuickStep, 1},
	NUMBER:       {0, GasQuickStep, 1},
	CALLDATASIZE: {0, GasQuickStep, 1},
	DIFFICULTY:   {0, GasQuickStep, 1},
	GASLIMIT:     {0, GasQuickStep, 1},
	POP:          {1, GasQuickStep, 0},
	PC:           {0, GasQuickStep, 1},
	MSIZE:        {0, GasQuickStep, 1},
	GAS:          {0, GasQuickStep, 1},
	BLOCKHASH:    {1, GasExtStep, 1},
	BALANCE:      {1, GasExtStep, 1},
	EXTCODESIZE:  {1, GasExtStep, 1},
	EXTCODECOPY:  {4, GasExtStep, 0},
	SLOAD:        {1, params.SloadGas, 1},
	SSTORE:       {2, Zero, 0},
	SHA3:         {2, params.Sha3Gas, 1},
	CREATE:       {3, params.CreateGas, 1},
	CALL:         {7, params.CallGas, 1},
	CALLCODE:     {7, params.CallGas, 1},
	JUMPDEST:     {0, params.JumpdestGas, 0},
	SUICIDE:      {1, Zero, 0},
	RETURN:       {2, Zero, 0},
	PUSH1:        {0, GasFastestStep, 1},
	DUP1:         {0, Zero, 1},
}
<|endoftext|>


<commit_before>package clienttest

import (
	"fmt"
	"time"

	check "gopkg.in/check.v1"

	"github.com/radanalyticsio/oshinko-cli/rest/client/clusters"
	"github.com/radanalyticsio/oshinko-cli/rest/helpers/errors"
	"github.com/radanalyticsio/oshinko-cli/rest/models"
)

type lessThanChecker struct {
	*check.CheckerInfo
}

// The LessThan checker attempts to determine if the observed value is
// less than the expected. It will only attempt this on values that can be
// cast as int64.
var LessThan check.Checker = &lessThanChecker{
	&check.CheckerInfo{Name: "LessThan", Params: []string{"obtained", "compare"}},
}

func (checker *lessThanChecker) Check(params []interface{}, names []string) (result bool, error string) {
	defer func() {
		if v := recover(); v != nil {
			result = false
			error = fmt.Sprint(v)
		}
	}()
	return params[0].(int) < params[1].(int), ""
}

func checkClusterHelper(s *OshinkoRestTestSuite, c *check.C, params *clusters.FindSingleClusterParams, count int) (obsmcount int64, obswcount int64) {
	const retries = 30
	var tries int

	for tries = 0; tries < retries; tries++ {
		cldresult, err := s.cli.Clusters.FindSingleCluster(params)
		if err != nil {
			msg := fmt.Sprintf("%s \n %+v",
				err.(*clusters.FindSingleClusterDefault).Error(),
				err.(*clusters.FindSingleClusterDefault).Payload.Errors)
			c.Fatal(msg)
		}

		// if enough pods are available, we loop through them and exit
		// the retry loop, otherwise sleep for 1 second.
		if len(cldresult.Payload.Cluster.Pods) >= count {
			// loop through the pods to count workers and masters
			obsmcount = 0
			obswcount = 0
			for _, pod := range cldresult.Payload.Cluster.Pods {
				switch *pod.Type {
				case "master":
					if *pod.Status == "Running" {
						obsmcount += 1
					}
				case "worker":
					if *pod.Status == "Running" {
						obswcount += 1
					}
				}
			}
			if int(obsmcount+obswcount) == count {
				break
			}
		}

		time.Sleep(1 * time.Second)
	}
	c.Assert(tries, LessThan, retries)
	return
}

func (s *OshinkoRestTestSuite) TestCreateAndDeleteCluster(c *check.C) {
	cname := "e2ecluster"
	mcount := int64(1)
	wcount := int64(3)

	cconfig := models.NewClusterConfig{MasterCount: mcount, WorkerCount: wcount}
	cdetails := models.NewCluster{Name: &cname, Config: &cconfig}
	clparams := clusters.NewCreateClusterParams().WithCluster(&cdetails)

	// create a cluster
	_, err := s.cli.Clusters.CreateCluster(clparams)
	if err != nil {
		msg := err.(*clusters.CreateClusterDefault).Error() + "\n"
		for _, e := range err.(*clusters.CreateClusterDefault).Payload.Errors {
			msg += errors.SingleErrorToString(e)
		}
		c.Fatal(msg)
	}

	// read the cluster details
	// because it may take some time for the pods to become available, we
	// must loop and try multiple times to read them. if we fail to read
	// them after a set number of retries, we consider the test to have
	// failed.
	cldparams := clusters.NewFindSingleClusterParams().WithName(cname)
	obsmcount, obswcount := checkClusterHelper(s, c, cldparams, int(mcount+wcount))

	c.Assert(obsmcount, check.Equals, mcount)
	c.Assert(obswcount, check.Equals, wcount)

	// scale up the cluster
	// this will attempt to scale up the number of workers by 1. as with
	// the creation test, this test will loop for a number of retries to
	// give time for the new worker to be created.
	uwcount := int64(wcount + 1)
	ucconfig := models.NewClusterConfig{MasterCount: mcount, WorkerCount: uwcount}
	ucdetails := models.NewCluster{Name: &cname, Config: &ucconfig}
	uclparams := clusters.NewUpdateSingleClusterParams().WithCluster(&ucdetails).WithName(cname)

	// update the cluster
	_, err = s.cli.Clusters.UpdateSingleCluster(uclparams)
	if err != nil {
		msg := err.(*clusters.UpdateSingleClusterDefault).Error() + "\n"
		for _, e := range err.(*clusters.UpdateSingleClusterDefault).Payload.Errors {
			msg += errors.SingleErrorToString(e)
		}
		c.Fatal(msg)
	}

	// check for update completion
	obsmcount, obswcount = checkClusterHelper(s, c, cldparams, int(mcount+uwcount))

	c.Assert(obsmcount, check.Equals, mcount)
	c.Assert(obswcount, check.Equals, uwcount)

	// scale down the cluster
	uwcount = int64(wcount - 1)
	ucconfig = models.NewClusterConfig{MasterCount: mcount, WorkerCount: uwcount}
	ucdetails = models.NewCluster{Name: &cname, Config: &ucconfig}
	uclparams = clusters.NewUpdateSingleClusterParams().WithCluster(&ucdetails).WithName(cname)

	// update the cluster
	_, err = s.cli.Clusters.UpdateSingleCluster(uclparams)
	if err != nil {
		msg := err.(*clusters.UpdateSingleClusterDefault).Error() + "\n"
		for _, e := range err.(*clusters.UpdateSingleClusterDefault).Payload.Errors {
			msg += errors.SingleErrorToString(e)
		}
		c.Fatal(msg)
	}

	// check for update completion
	obsmcount, obswcount = checkClusterHelper(s, c, cldparams, int(mcount+uwcount))

	c.Assert(obsmcount, check.Equals, mcount)
	c.Assert(obswcount, check.Equals, uwcount)

	// delete the cluster
	delparams := clusters.NewDeleteSingleClusterParams().WithName(cname)
	_, err = s.cli.Clusters.DeleteSingleCluster(delparams)
	if err != nil {
		switch err.(type) {
		case *clusters.DeleteSingleClusterDefault:
			c.Fatal(err.(*clusters.DeleteSingleClusterDefault).Error())
		default:
			c.Fatal(err)
		}
	}

	// confirm delete
	var tries int
	const retries = 30
	obsclcount := 0
	for tries = 0; tries < retries; tries++ {
		fcresult, err := s.cli.Clusters.FindClusters(nil)
		if err != nil {
			c.Fatal(err.(*clusters.FindClustersDefault).Error())
		}
		if obsclcount = len(fcresult.Payload.Clusters); obsclcount == 0 {
			break
		}
		time.Sleep(1 * time.Second)
	}

	c.Assert(tries, LessThan, retries)
	expclcount := 0
	c.Assert(obsclcount, check.Equals, expclcount)
}
<commit_msg>Fix the client tests to work with counts as pointers<commit_after>package clienttest

import (
	"fmt"
	"time"

	check "gopkg.in/check.v1"

	"github.com/radanalyticsio/oshinko-cli/rest/client/clusters"
	"github.com/radanalyticsio/oshinko-cli/rest/helpers/errors"
	"github.com/radanalyticsio/oshinko-cli/rest/models"
)

type lessThanChecker struct {
	*check.CheckerInfo
}

// The LessThan checker attempts to determine if the observed value is
// less than the expected. It will only attempt this on values that can be
// cast as int64.
var LessThan check.Checker = &lessThanChecker{
	&check.CheckerInfo{Name: "LessThan", Params: []string{"obtained", "compare"}},
}

func (checker *lessThanChecker) Check(params []interface{}, names []string) (result bool, error string) {
	defer func() {
		if v := recover(); v != nil {
			result = false
			error = fmt.Sprint(v)
		}
	}()
	return params[0].(int) < params[1].(int), ""
}

func checkClusterHelper(s *OshinkoRestTestSuite, c *check.C, params *clusters.FindSingleClusterParams, count int) (obsmcount int64, obswcount int64) {
	const retries = 30
	var tries int

	for tries = 0; tries < retries; tries++ {
		cldresult, err := s.cli.Clusters.FindSingleCluster(params)
		if err != nil {
			msg := fmt.Sprintf("%s \n %+v",
				err.(*clusters.FindSingleClusterDefault).Error(),
				err.(*clusters.FindSingleClusterDefault).Payload.Errors)
			c.Fatal(msg)
		}

		// if enough pods are available, we loop through them and exit
		// the retry loop, otherwise sleep for 1 second.
		if len(cldresult.Payload.Cluster.Pods) >= count {
			// loop through the pods to count workers and masters
			obsmcount = 0
			obswcount = 0
			for _, pod := range cldresult.Payload.Cluster.Pods {
				switch *pod.Type {
				case "master":
					if *pod.Status == "Running" {
						obsmcount += 1
					}
				case "worker":
					if *pod.Status == "Running" {
						obswcount += 1
					}
				}
			}
			if int(obsmcount+obswcount) == count {
				break
			}
		}

		time.Sleep(1 * time.Second)
	}
	c.Assert(tries, LessThan, retries)
	return
}

func (s *OshinkoRestTestSuite) TestCreateAndDeleteCluster(c *check.C) {
	cname := "e2ecluster"
	mcount := int64(1)
	wcount := int64(3)

	cconfig := models.NewClusterConfig{MasterCount: &mcount, WorkerCount: &wcount}
	cdetails := models.NewCluster{Name: &cname, Config: &cconfig}
	clparams := clusters.NewCreateClusterParams().WithCluster(&cdetails)

	// create a cluster
	_, err := s.cli.Clusters.CreateCluster(clparams)
	if err != nil {
		msg := err.(*clusters.CreateClusterDefault).Error() + "\n"
		for _, e := range err.(*clusters.CreateClusterDefault).Payload.Errors {
			msg += errors.SingleErrorToString(e)
		}
		c.Fatal(msg)
	}

	// read the cluster details
	// because it may take some time for the pods to become available, we
	// must loop and try multiple times to read them. if we fail to read
	// them after a set number of retries, we consider the test to have
	// failed.
	cldparams := clusters.NewFindSingleClusterParams().WithName(cname)
	obsmcount, obswcount := checkClusterHelper(s, c, cldparams, int(mcount+wcount))

	c.Assert(obsmcount, check.Equals, mcount)
	c.Assert(obswcount, check.Equals, wcount)

	// scale up the cluster
	// this will attempt to scale up the number of workers by 1. as with
	// the creation test, this test will loop for a number of retries to
	// give time for the new worker to be created.
	uwcount := int64(wcount + 1)
	ucconfig := models.NewClusterConfig{MasterCount: &mcount, WorkerCount: &uwcount}
	ucdetails := models.NewCluster{Name: &cname, Config: &ucconfig}
	uclparams := clusters.NewUpdateSingleClusterParams().WithCluster(&ucdetails).WithName(cname)

	// update the cluster
	_, err = s.cli.Clusters.UpdateSingleCluster(uclparams)
	if err != nil {
		msg := err.(*clusters.UpdateSingleClusterDefault).Error() + "\n"
		for _, e := range err.(*clusters.UpdateSingleClusterDefault).Payload.Errors {
			msg += errors.SingleErrorToString(e)
		}
		c.Fatal(msg)
	}

	// check for update completion
	obsmcount, obswcount = checkClusterHelper(s, c, cldparams, int(mcount+uwcount))

	c.Assert(obsmcount, check.Equals, mcount)
	c.Assert(obswcount, check.Equals, uwcount)

	// scale down the cluster
	uwcount = int64(wcount - 1)
	ucconfig = models.NewClusterConfig{MasterCount: &mcount, WorkerCount: &uwcount}
	ucdetails = models.NewCluster{Name: &cname, Config: &ucconfig}
	uclparams = clusters.NewUpdateSingleClusterParams().WithCluster(&ucdetails).WithName(cname)

	// update the cluster
	_, err = s.cli.Clusters.UpdateSingleCluster(uclparams)
	if err != nil {
		msg := err.(*clusters.UpdateSingleClusterDefault).Error() + "\n"
		for _, e := range err.(*clusters.UpdateSingleClusterDefault).Payload.Errors {
			msg += errors.SingleErrorToString(e)
		}
		c.Fatal(msg)
	}

	// check for update completion
	obsmcount, obswcount = checkClusterHelper(s, c, cldparams, int(mcount+uwcount))

	c.Assert(obsmcount, check.Equals, mcount)
	c.Assert(obswcount, check.Equals, uwcount)

	// delete the cluster
	delparams := clusters.NewDeleteSingleClusterParams().WithName(cname)
	_, err = s.cli.Clusters.DeleteSingleCluster(delparams)
	if err != nil {
		switch err.(type) {
		case *clusters.DeleteSingleClusterDefault:
			c.Fatal(err.(*clusters.DeleteSingleClusterDefault).Error())
		default:
			c.Fatal(err)
		}
	}

	// confirm delete
	var tries int
	const retries = 30
	obsclcount := 0
	for tries = 0; tries < retries; tries++ {
		fcresult, err := s.cli.Clusters.FindClusters(nil)
		if err != nil {
			c.Fatal(err.(*clusters.FindClustersDefault).Error())
		}
		if obsclcount = len(fcresult.Payload.Clusters); obsclcount == 0 {
			break
		}
		time.Sleep(1 * time.Second)
	}

	c.Assert(tries, LessThan, retries)
	expclcount := 0
	c.Assert(obsclcount, check.Equals, expclcount)
}
<|endoftext|>


<commit_before>package localbinary

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"os/exec"
	"strings"
	"time"

	"github.com/docker/machine/libmachine/log"
)

var (
	// Timeout where we will bail if we're not able to properly contact the
	// plugin server.
	defaultTimeout = 10 * time.Second
	CoreDrivers    = [...]string{"amazonec2", "azure", "digitalocean",
		"exoscale", "generic", "google", "hyperv", "none", "openstack",
		"rackspace", "softlayer", "virtualbox", "vmwarefusion",
		"vmwarevcloudair", "vmwarevsphere"}
)

const (
	pluginOut           = "(%s) %s"
	pluginErr           = "(%s) DBG | %s"
	PluginEnvKey        = "MACHINE_PLUGIN_TOKEN"
	PluginEnvVal        = "42"
	PluginEnvDriverName = "MACHINE_PLUGIN_DRIVER_NAME"
)

type PluginStreamer interface {
	// Return a channel for receiving the output of the stream line by
	// line, and a channel for stopping the stream when we are finished
	// reading from it.
	//
	// It happens to be the case that we do this all inside of the main
	// plugin struct today, but that may not be the case forever.
	AttachStream(*bufio.Scanner) (<-chan string, chan<- bool)
}

type PluginServer interface {
	// Get the address where the plugin server is listening.
	Address() (string, error)

	// Serve kicks off the plugin server.
	Serve() error

	// Close shuts down the initialized server.
	Close() error
}

type McnBinaryExecutor interface {
	// Execute the driver plugin.  Returns scanners for plugin binary
	// stdout and stderr.
	Start() (*bufio.Scanner, *bufio.Scanner, error)

	// Stop reading from the plugins in question.
	Close() error
}

// DriverPlugin interface wraps the underlying mechanics of starting a driver
// plugin server and then figuring out where it can be dialed.
type DriverPlugin interface {
	PluginServer
	PluginStreamer
}

type Plugin struct {
	Executor    McnBinaryExecutor
	Addr        string
	MachineName string
	addrCh      chan string
	stopCh      chan bool
	timeout     time.Duration
}

type Executor struct {
	pluginStdout, pluginStderr io.ReadCloser
	DriverName                 string
	cmd                        *exec.Cmd
	binaryPath                 string
}

type ErrPluginBinaryNotFound struct {
	driverName string
}

func (e ErrPluginBinaryNotFound) Error() string {
	return fmt.Sprintf("Driver %q not found. Do you have the plugin binary accessible in your PATH?", e.driverName)
}

func driverPath(driverName string) string {
	for _, coreDriver := range CoreDrivers {
		if coreDriver == driverName {
			return os.Args[0] // "docker-machine"
		}
	}

	return fmt.Sprintf("docker-machine-driver-%s", driverName)
}

func NewPlugin(driverName string) (*Plugin, error) {
	driverPath := driverPath(driverName)
	binaryPath, err := exec.LookPath(driverPath)
	if err != nil {
		return nil, ErrPluginBinaryNotFound{driverName}
	}

	log.Debugf("Found binary path at %s", binaryPath)

	return &Plugin{
		stopCh: make(chan bool),
		addrCh: make(chan string, 1),
		Executor: &Executor{
			DriverName: driverName,
			binaryPath: binaryPath,
		},
	}, nil
}

func (lbe *Executor) Start() (*bufio.Scanner, *bufio.Scanner, error) {
	var err error

	log.Debugf("Launching plugin server for driver %s", lbe.DriverName)

	lbe.cmd = exec.Command(lbe.binaryPath)

	lbe.pluginStdout, err = lbe.cmd.StdoutPipe()
	if err != nil {
		return nil, nil, fmt.Errorf("Error getting cmd stdout pipe: %s", err)
	}

	lbe.pluginStderr, err = lbe.cmd.StderrPipe()
	if err != nil {
		return nil, nil, fmt.Errorf("Error getting cmd stderr pipe: %s", err)
	}

	outScanner := bufio.NewScanner(lbe.pluginStdout)
	errScanner := bufio.NewScanner(lbe.pluginStderr)

	os.Setenv(PluginEnvKey, PluginEnvVal)
	os.Setenv(PluginEnvDriverName, lbe.DriverName)

	if err := lbe.cmd.Start(); err != nil {
		return nil, nil, fmt.Errorf("Error starting plugin binary: %s", err)
	}

	return outScanner, errScanner, nil
}

func (lbe *Executor) Close() error {
	if err := lbe.cmd.Wait(); err != nil {
		return fmt.Errorf("Error waiting for binary close: %s", err)
	}

	if err := lbe.pluginStdout.Close(); err != nil {
		return fmt.Errorf("Error closing plugin stdout: %s", err)
	}

	if err := lbe.pluginStderr.Close(); err != nil {
		return fmt.Errorf("Error closing plugin stderr: %s", err)
	}

	return nil
}

func stream(scanner *bufio.Scanner, streamOutCh chan<- string, stopCh <-chan bool) {
	for {
		select {
		case <-stopCh:
			close(streamOutCh)
			return
		default:
			if scanner.Scan() {
				line := scanner.Text()
				if err := scanner.Err(); err != nil {
					log.Warnf("Scanning stream: %s", err)
				}
				streamOutCh <- strings.Trim(line, "\n")
			}
		}
	}
}

func (lbp *Plugin) AttachStream(scanner *bufio.Scanner) (<-chan string, chan<- bool) {
	streamOutCh := make(chan string)
	stopCh := make(chan bool)
	go stream(scanner, streamOutCh, stopCh)
	return streamOutCh, stopCh
}

func (lbp *Plugin) execServer() error {
	outScanner, errScanner, err := lbp.Executor.Start()
	if err != nil {
		return err
	}

	// Scan just one line to get the address, then send it to the relevant
	// channel.
	outScanner.Scan()
	addr := outScanner.Text()
	if err := outScanner.Err(); err != nil {
		return fmt.Errorf("Reading plugin address failed: %s", err)
	}

	lbp.addrCh <- strings.TrimSpace(addr)

	stdOutCh, stopStdoutCh := lbp.AttachStream(outScanner)
	stdErrCh, stopStderrCh := lbp.AttachStream(errScanner)

	for {
		select {
		case out := <-stdOutCh:
			log.Infof(pluginOut, lbp.MachineName, out)
		case err := <-stdErrCh:
			log.Debugf(pluginErr, lbp.MachineName, err)
		case _ = <-lbp.stopCh:
			stopStdoutCh <- true
			stopStderrCh <- true
			if err := lbp.Executor.Close(); err != nil {
				return fmt.Errorf("Error closing local plugin binary: %s", err)
			}
			return nil
		}
	}
}

func (lbp *Plugin) Serve() error {
	return lbp.execServer()
}

func (lbp *Plugin) Address() (string, error) {
	if lbp.Addr == "" {
		if lbp.timeout == 0 {
			lbp.timeout = defaultTimeout
		}

		select {
		case lbp.Addr = <-lbp.addrCh:
			log.Debugf("Plugin server listening at address %s", lbp.Addr)
			close(lbp.addrCh)
			return lbp.Addr, nil
		case <-time.After(lbp.timeout):
			return "", fmt.Errorf("Failed to dial the plugin server in %s", lbp.timeout)
		}
	}
	return lbp.Addr, nil
}

func (lbp *Plugin) Close() error {
	lbp.stopCh <- true
	return nil
}
<commit_msg>Fix race condition. cmdWait will do the closing<commit_after>package localbinary

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"os/exec"
	"strings"
	"time"

	"github.com/docker/machine/libmachine/log"
)

var (
	// Timeout where we will bail if we're not able to properly contact the
	// plugin server.
	defaultTimeout = 10 * time.Second
	CoreDrivers    = [...]string{"amazonec2", "azure", "digitalocean",
		"exoscale", "generic", "google", "hyperv", "none", "openstack",
		"rackspace", "softlayer", "virtualbox", "vmwarefusion",
		"vmwarevcloudair", "vmwarevsphere"}
)

const (
	pluginOut           = "(%s) %s"
	pluginErr           = "(%s) DBG | %s"
	PluginEnvKey        = "MACHINE_PLUGIN_TOKEN"
	PluginEnvVal        = "42"
	PluginEnvDriverName = "MACHINE_PLUGIN_DRIVER_NAME"
)

type PluginStreamer interface {
	// Return a channel for receiving the output of the stream line by
	// line.
	//
	// It happens to be the case that we do this all inside of the main
	// plugin struct today, but that may not be the case forever.
	AttachStream(*bufio.Scanner) <-chan string
}

type PluginServer interface {
	// Get the address where the plugin server is listening.
	Address() (string, error)

	// Serve kicks off the plugin server.
	Serve() error

	// Close shuts down the initialized server.
	Close() error
}

type McnBinaryExecutor interface {
	// Execute the driver plugin.  Returns scanners for plugin binary
	// stdout and stderr.
	Start() (*bufio.Scanner, *bufio.Scanner, error)

	// Stop reading from the plugins in question.
	Close() error
}

// DriverPlugin interface wraps the underlying mechanics of starting a driver
// plugin server and then figuring out where it can be dialed.
type DriverPlugin interface {
	PluginServer
	PluginStreamer
}

type Plugin struct {
	Executor    McnBinaryExecutor
	Addr        string
	MachineName string
	addrCh      chan string
	stopCh      chan bool
	timeout     time.Duration
}

type Executor struct {
	pluginStdout, pluginStderr io.ReadCloser
	DriverName                 string
	cmd                        *exec.Cmd
	binaryPath                 string
}

type ErrPluginBinaryNotFound struct {
	driverName string
}

func (e ErrPluginBinaryNotFound) Error() string {
	return fmt.Sprintf("Driver %q not found. Do you have the plugin binary accessible in your PATH?", e.driverName)
}

func driverPath(driverName string) string {
	for _, coreDriver := range CoreDrivers {
		if coreDriver == driverName {
			return os.Args[0] // "docker-machine"
		}
	}

	return fmt.Sprintf("docker-machine-driver-%s", driverName)
}

func NewPlugin(driverName string) (*Plugin, error) {
	driverPath := driverPath(driverName)
	binaryPath, err := exec.LookPath(driverPath)
	if err != nil {
		return nil, ErrPluginBinaryNotFound{driverName}
	}

	log.Debugf("Found binary path at %s", binaryPath)

	return &Plugin{
		stopCh: make(chan bool),
		addrCh: make(chan string, 1),
		Executor: &Executor{
			DriverName: driverName,
			binaryPath: binaryPath,
		},
	}, nil
}

func (lbe *Executor) Start() (*bufio.Scanner, *bufio.Scanner, error) {
	var err error

	log.Debugf("Launching plugin server for driver %s", lbe.DriverName)

	lbe.cmd = exec.Command(lbe.binaryPath)

	lbe.pluginStdout, err = lbe.cmd.StdoutPipe()
	if err != nil {
		return nil, nil, fmt.Errorf("Error getting cmd stdout pipe: %s", err)
	}

	lbe.pluginStderr, err = lbe.cmd.StderrPipe()
	if err != nil {
		return nil, nil, fmt.Errorf("Error getting cmd stderr pipe: %s", err)
	}

	outScanner := bufio.NewScanner(lbe.pluginStdout)
	errScanner := bufio.NewScanner(lbe.pluginStderr)

	os.Setenv(PluginEnvKey, PluginEnvVal)
	os.Setenv(PluginEnvDriverName, lbe.DriverName)

	if err := lbe.cmd.Start(); err != nil {
		return nil, nil, fmt.Errorf("Error starting plugin binary: %s", err)
	}

	return outScanner, errScanner, nil
}

func (lbe *Executor) Close() error {
	if err := lbe.cmd.Wait(); err != nil {
		return fmt.Errorf("Error waiting for binary close: %s", err)
	}

	return nil
}

func stream(scanner *bufio.Scanner, streamOutCh chan<- string) {
	for scanner.Scan() {
		line := scanner.Text()
		if err := scanner.Err(); err != nil {
			log.Warnf("Scanning stream: %s", err)
		}
		streamOutCh <- strings.Trim(line, "\n")
	}
}

func (lbp *Plugin) AttachStream(scanner *bufio.Scanner) <-chan string {
	streamOutCh := make(chan string)
	go stream(scanner, streamOutCh)
	return streamOutCh
}

func (lbp *Plugin) execServer() error {
	outScanner, errScanner, err := lbp.Executor.Start()
	if err != nil {
		return err
	}

	// Scan just one line to get the address, then send it to the relevant
	// channel.
	outScanner.Scan()
	addr := outScanner.Text()
	if err := outScanner.Err(); err != nil {
		return fmt.Errorf("Reading plugin address failed: %s", err)
	}

	lbp.addrCh <- strings.TrimSpace(addr)

	stdOutCh := lbp.AttachStream(outScanner)
	stdErrCh := lbp.AttachStream(errScanner)

	for {
		select {
		case out := <-stdOutCh:
			log.Infof(pluginOut, lbp.MachineName, out)
		case err := <-stdErrCh:
			log.Debugf(pluginErr, lbp.MachineName, err)
		case <-lbp.stopCh:
			if err := lbp.Executor.Close(); err != nil {
				return fmt.Errorf("Error closing local plugin binary: %s", err)
			}
			return nil
		}
	}
}

func (lbp *Plugin) Serve() error {
	return lbp.execServer()
}

func (lbp *Plugin) Address() (string, error) {
	if lbp.Addr == "" {
		if lbp.timeout == 0 {
			lbp.timeout = defaultTimeout
		}

		select {
		case lbp.Addr = <-lbp.addrCh:
			log.Debugf("Plugin server listening at address %s", lbp.Addr)
			close(lbp.addrCh)
			return lbp.Addr, nil
		case <-time.After(lbp.timeout):
			return "", fmt.Errorf("Failed to dial the plugin server in %s", lbp.timeout)
		}
	}
	return lbp.Addr, nil
}

func (lbp *Plugin) Close() error {
	lbp.stopCh <- true
	return nil
}
<|endoftext|>


<commit_before>package install

import (
	"log"
	"os"
	"os/user"
)

const script = `#!/usr/bin/env bash
dtags=~/.config/dtags/dtags
declare -a arr=("" "add" "del" "tags" "list" "ls" "completion", "bash-script")
found=0

# check if arg is a single arg command in arr array
for i in "${arr[@]}"; do
    if [[ $i == $1 ]]; then
        found=1
        ${dtags} $@
        false 1
    fi
done

if [[ -d "$(${dtags} $1)" && ${found} -ne 1 ]]; then
    cd "$(${dtags} $1)"
elif [[ ${found} -ne 1 ]]; then
    echo "no directory found for tag [$1]"
    false 1
fi`

func check(e error) {
	if e != nil {
		log.Fatal(e)
	}
}

func WriteFile() (string, error) {

	// get user info
	u, err := user.Current()
	check(err)

	file := u.HomeDir + "/.config/dtags/dt"

	// create file
	f, err := os.Create(file)
	check(err)
	err = os.Chmod(file, 0755)
	check(err)
	defer f.Close()

	// write script to file
	f.Write([]byte(script))

	return script, nil
}
<commit_msg>update bash helper script<commit_after>package install

import (
	"log"
	"os"
	"os/user"
)

const script = `#!/usr/bin/env bash
dtags=dtags
declare -a arr=("" "add" "del" "tags" "list" "ls" "completion", "bash-script")
found=0

# check if arg is a single arg command in arr array
for i in "${arr[@]}"; do
    if [[ $i == $1 ]]; then
        found=1
        ${dtags} $@
        false 1
    fi
done

if [[ -d "$(${dtags} $1)" && ${found} -ne 1 ]]; then
    cd "$(${dtags} $1)"
elif [[ ${found} -ne 1 ]]; then
    echo "no directory found for tag [$1]"
    false 1
fi`

func check(e error) {
	if e != nil {
		log.Fatal(e)
	}
}

func WriteFile() (string, error) {

	// get user info
	u, err := user.Current()
	check(err)

	file := u.HomeDir + "/.config/dtags/dt"

	// create file
	f, err := os.Create(file)
	check(err)
	err = os.Chmod(file, 0755)
	check(err)
	defer f.Close()

	// write script to file
	f.Write([]byte(script))

	return script, nil
}
<|endoftext|>


<commit_before>package kafka

import (
	"errors"
	"fmt"
	"regexp"
	"strconv"
	"strings"

	exporter "github.com/kawamuray/prometheus-kafka-consumer-group-exporter"
	"github.com/prometheus/common/log"
)

func parseGroups(output CommandOutput) ([]string, error) {
	if strings.Contains(output.Stderr, "java.lang.RuntimeException") {
		return nil, fmt.Errorf("Got runtime error when executing script. Output: %s", output)
	}

	lines := strings.Split(output.Stdout, "\n")
	groups := make([]string, 0, len(lines))
	for _, line := range lines {
		if line != "" {
			groups = append(groups, line)
		}
	}

	return groups, nil
}

type regexpParser struct {
	// header is the expected format of the first line. Used as fingerprint to distinguish if this parser is the right one.
	header *regexp.Regexp
	// line is the regexp used for the remaining lines of the output (as long as Header matched correctly).
	line *regexp.Regexp
	//
	indexByName map[string]int
}

func newRegexpParser(header, line *regexp.Regexp) (*regexpParser, error) {
	indexByName := make(map[string]int)
	for i, name := range line.SubexpNames() {
		indexByName[name] = i
	}

	for _, field := range []string{"topic", "partitionId", "currentOffset", "lag", "clientId", "consumerAddress"} {
		if _, exists := indexByName[field]; !exists {
			return nil, fmt.Errorf("line regexp missing '%s' capturing group", field)
		}
	}

	return &regexpParser{header.Copy(), line.Copy(), indexByName}, nil
}

func (p *regexpParser) Parse(output CommandOutput) ([]exporter.PartitionInfo, error) {
	lines := removeEmptyLines(strings.Split(output.Stdout, "\n"))
	if len(lines) == 0 {
		return nil, errors.New("empty output. stderr: " + output.Stderr)
	}
	headerLine := lines[0]
	dataLines := lines[1:]

	if !p.header.MatchString(headerLine) {
		return nil, errors.New("incorrect header")
	}

	partitions := make([]exporter.PartitionInfo, 0, len(dataLines))
	var err error
	var partition *exporter.PartitionInfo
	for _, line := range dataLines {
		partition, err = p.parseLine(line)
		if err != nil {
			break
		}
		partitions = append(partitions, *partition)
	}
	return partitions, err
}

func removeEmptyLines(s []string) []string {
	output := make([]string, 0, len(s))
	for _, e := range s {
		if strings.Trim(e, " ") != "" {
			output = append(output, e)
		}
	}
	return output
}

func (p *regexpParser) String() string {
	return fmt.Sprintf("regexpParser{header: `%s`, line: `%s`}", p.header, p.line)
}

func (p *regexpParser) parseLine(line string) (*exporter.PartitionInfo, error) {
	matches := p.line.FindStringSubmatch(line)

	var err error

	var lag int64
	if lagIndex := p.indexByName["lag"]; lagIndex >= len(matches) {
		lag = -1
		err = fmt.Errorf("unable to find current offset field. line: %s", line)
		log.Warn(err.Error())
	} else {
		lag, err = parseLong(matches[lagIndex])
	}
	if err != nil {
		log.Warn("unable to parse int for lag. line: %s", line)
	}

	var currentOffset int64
	if currentOffsetIndex := p.indexByName["currentOffset"]; currentOffsetIndex >= len(matches) {
		currentOffset = -1
		err = fmt.Errorf("unable to find current offset field. Line: %s", line)
		log.Warn(err.Error())
	} else {
		currentOffset, err = parseLong(matches[currentOffsetIndex])
	}
	if err != nil {
		log.Warn("unable to parse int for current offset. Line: %s", line)
	}

	partitionInfo := &exporter.PartitionInfo{
		Topic:           matches[p.indexByName["topic"]],
		PartitionID:     matches[p.indexByName["partitionId"]],
		CurrentOffset:   currentOffset,
		Lag:             lag,
		ClientID:        matches[p.indexByName["clientId"]],
		ConsumerAddress: matches[p.indexByName["consumerAddress"]],
	}

	return partitionInfo, nil
}

func parseLong(value string) (int64, error) {
	longVal, err := strconv.ParseInt(value, 10, 64)
	if err != nil {
		return -1, err
	}
	return longVal, nil
}

// Parsers for "describe group" output.
var (
	// Parser for Kafka 0.10.2.1. Since we are unsure if the column widths are dynamic, we are using `\s+` for delimiters.
	kafka0_10_2_1DescribeGroupParser = mustBuildNewRegexpParser(
		regexp.MustCompile(`TOPIC\s+PARTITION\s+CURRENT-OFFSET\s+LOG-END-OFFSET\s+LAG\s+CONSUMER-ID\s+HOST\s+CLIENT-ID`),
		// "topic", "partitionId", "currentOffset", "lag", "clientId", "consumerAddress"
		regexp.MustCompile(`(?P<topic>[a-zA-Z0-9\\._\\-]+)\s+(?P<partitionId>\d+)\s+(?P<currentOffset>\d+)\s+\d+\s+(?P<lag>\d+)\s+(?P<consumerId>\S+)\s+(?P<consumerAddress>\S+)\s+(?P<clientId>\S+)`),
	)

	// Parser for Kafka 0.10.1.X.
	kafka0_10_1DescribeGroupParser = mustBuildNewRegexpParser(
		regexp.MustCompile(`GROUP\s+TOPIC\s+PARTITION\s+CURRENT-OFFSET\s+LOG-END-OFFSET\s+LAG\s+OWNER`),
		regexp.MustCompile(`.+\s+(?P<topic>[a-zA-Z0-9\\._\\-]+)\s+(?P<partitionId>\d+)\s+(?P<currentOffset>\d+)\s+\d+\s+(?P<lag>\d+)\s+(?P<clientId>\S+)_/(?P<consumerAddress>.+)`),
	)

	// Parser for Kafka 0.10.0.1. Since we are unsure if the column widths are dynamic, we are using `\s+` for delimiters.
	kafka0_10_0_1DescribeGroupParser = mustBuildNewRegexpParser(
		regexp.MustCompile(`GROUP\s+TOPIC\s+PARTITION\s+CURRENT-OFFSET\s+LOG-END-OFFSET\s+LAG\s+OWNER`),
		regexp.MustCompile(`.+\s+(?P<topic>[a-zA-Z0-9\\._\\-]+)\s+(?P<partitionId>\d+)\s+(?P<currentOffset>\d+)\s+\d+\s+(?P<lag>\d+)\s+(?P<clientId>\S+)_/(?P<consumerAddress>.+)`),
	)
	kafka0_9_0_1DescribeGroupParser = mustBuildNewRegexpParser(
		regexp.MustCompile("GROUP, TOPIC, PARTITION, CURRENT OFFSET, LOG END OFFSET, LAG, OWNER"),
		regexp.MustCompile(`[^,]+, (?P<topic>[a-zA-Z0-9\\._\\-]+), (?P<partitionId>\d+), (?P<currentOffset>\d+), \d+, (?P<lag>\d+), (?P<clientId>.+)_/(?P<consumerAddress>.+)`),
	)
)

func mustBuildNewRegexpParser(header, line *regexp.Regexp) *regexpParser {
	parser, err := newRegexpParser(header, line)
	if err != nil {
		log.Fatal(err)
	}
	return parser
}

// DefaultDescribeGroupParser returns a DelegatingParser consisting of all formats known.
func DefaultDescribeGroupParser() *DelegatingParser {
	return &DelegatingParser{
		[]DescribeGroupParser{
			kafka0_9_0_1DescribeGroupParser,
			kafka0_10_0_1DescribeGroupParser,
			kafka0_10_1DescribeGroupParser,
			kafka0_10_2_1DescribeGroupParser,
		},
	}
}

// DelegatingParser is a parser that tries multiple parser returning the first
// succesful parsed result.
type DelegatingParser struct {
	Parsers []DescribeGroupParser
}

// Parse parses the output. It tries each Parser in order, returning an error
// if all fails.
func (p *DelegatingParser) Parse(output CommandOutput) ([]exporter.PartitionInfo, error) {
	var err error
	var partitions []exporter.PartitionInfo

	for _, parser := range p.Parsers {
		partitions, err = parser.Parse(output)
		if err == nil {
			return partitions, err
		}
	}

	return nil, errors.New("no parser could parse the output")
}

func (p *DelegatingParser) String() string {
	return "DefaultParser"
}
<commit_msg>fix(parsing): return parsing error<commit_after>package kafka

import (
	"errors"
	"fmt"
	"regexp"
	"strconv"
	"strings"

	exporter "github.com/kawamuray/prometheus-kafka-consumer-group-exporter"
	"github.com/prometheus/common/log"
)

func parseGroups(output CommandOutput) ([]string, error) {
	if strings.Contains(output.Stderr, "java.lang.RuntimeException") {
		return nil, fmt.Errorf("Got runtime error when executing script. Output: %s", output)
	}

	lines := strings.Split(output.Stdout, "\n")
	groups := make([]string, 0, len(lines))
	for _, line := range lines {
		if line != "" {
			groups = append(groups, line)
		}
	}

	return groups, nil
}

type regexpParser struct {
	// header is the expected format of the first line. Used as fingerprint to distinguish if this parser is the right one.
	header *regexp.Regexp
	// line is the regexp used for the remaining lines of the output (as long as Header matched correctly).
	line *regexp.Regexp
	//
	indexByName map[string]int
}

func newRegexpParser(header, line *regexp.Regexp) (*regexpParser, error) {
	indexByName := make(map[string]int)
	for i, name := range line.SubexpNames() {
		indexByName[name] = i
	}

	for _, field := range []string{"topic", "partitionId", "currentOffset", "lag", "clientId", "consumerAddress"} {
		if _, exists := indexByName[field]; !exists {
			return nil, fmt.Errorf("line regexp missing '%s' capturing group", field)
		}
	}

	return &regexpParser{header.Copy(), line.Copy(), indexByName}, nil
}

func (p *regexpParser) Parse(output CommandOutput) ([]exporter.PartitionInfo, error) {
	lines := removeEmptyLines(strings.Split(output.Stdout, "\n"))
	if len(lines) == 0 {
		return nil, errors.New("empty output. stderr: " + output.Stderr)
	}
	headerLine := lines[0]
	dataLines := lines[1:]

	if !p.header.MatchString(headerLine) {
		return nil, errors.New("incorrect header")
	}

	partitions := make([]exporter.PartitionInfo, 0, len(dataLines))
	var err error
	var partition *exporter.PartitionInfo
	for _, line := range dataLines {
		partition, err = p.parseLine(line)
		if err != nil {
			break
		}
		partitions = append(partitions, *partition)
	}
	return partitions, err
}

func removeEmptyLines(s []string) []string {
	output := make([]string, 0, len(s))
	for _, e := range s {
		if strings.Trim(e, " ") != "" {
			output = append(output, e)
		}
	}
	return output
}

func (p *regexpParser) String() string {
	return fmt.Sprintf("regexpParser{header: `%s`, line: `%s`}", p.header, p.line)
}

func (p *regexpParser) parseLine(line string) (*exporter.PartitionInfo, error) {
	matches := p.line.FindStringSubmatch(line)

	var err error

	var lag int64
	if lagIndex := p.indexByName["lag"]; lagIndex >= len(matches) {
		lag = -1
		err = fmt.Errorf("unable to find current offset field. line: %s", line)
		log.Warn(err.Error())
	} else {
		lag, err = parseLong(matches[lagIndex])
	}
	if err != nil {
		log.Warn("unable to parse int for lag. line: %s", line)
	}

	var currentOffset int64
	if currentOffsetIndex := p.indexByName["currentOffset"]; currentOffsetIndex >= len(matches) {
		currentOffset = -1
		err = fmt.Errorf("unable to find current offset field. Line: %s", line)
		log.Warn(err.Error())
	} else {
		currentOffset, err = parseLong(matches[currentOffsetIndex])
	}
	if err != nil {
		log.Warn("unable to parse int for current offset. Line: %s", line)
	}

	partitionInfo := &exporter.PartitionInfo{
		Topic:           matches[p.indexByName["topic"]],
		PartitionID:     matches[p.indexByName["partitionId"]],
		CurrentOffset:   currentOffset,
		Lag:             lag,
		ClientID:        matches[p.indexByName["clientId"]],
		ConsumerAddress: matches[p.indexByName["consumerAddress"]],
	}

	return partitionInfo, err
}

func parseLong(value string) (int64, error) {
	longVal, err := strconv.ParseInt(value, 10, 64)
	if err != nil {
		return -1, err
	}
	return longVal, nil
}

// Parsers for "describe group" output.
var (
	// Parser for Kafka 0.10.2.1. Since we are unsure if the column widths are dynamic, we are using `\s+` for delimiters.
	kafka0_10_2_1DescribeGroupParser = mustBuildNewRegexpParser(
		regexp.MustCompile(`TOPIC\s+PARTITION\s+CURRENT-OFFSET\s+LOG-END-OFFSET\s+LAG\s+CONSUMER-ID\s+HOST\s+CLIENT-ID`),
		// "topic", "partitionId", "currentOffset", "lag", "clientId", "consumerAddress"
		regexp.MustCompile(`(?P<topic>[a-zA-Z0-9\\._\\-]+)\s+(?P<partitionId>\d+)\s+(?P<currentOffset>\d+)\s+\d+\s+(?P<lag>\d+)\s+(?P<consumerId>\S+)\s+(?P<consumerAddress>\S+)\s+(?P<clientId>\S+)`),
	)

	// Parser for Kafka 0.10.1.X.
	kafka0_10_1DescribeGroupParser = mustBuildNewRegexpParser(
		regexp.MustCompile(`GROUP\s+TOPIC\s+PARTITION\s+CURRENT-OFFSET\s+LOG-END-OFFSET\s+LAG\s+OWNER`),
		regexp.MustCompile(`.+\s+(?P<topic>[a-zA-Z0-9\\._\\-]+)\s+(?P<partitionId>\d+)\s+(?P<currentOffset>\d+)\s+\d+\s+(?P<lag>\d+)\s+(?P<clientId>\S+)_/(?P<consumerAddress>.+)`),
	)

	// Parser for Kafka 0.10.0.1. Since we are unsure if the column widths are dynamic, we are using `\s+` for delimiters.
	kafka0_10_0_1DescribeGroupParser = mustBuildNewRegexpParser(
		regexp.MustCompile(`GROUP\s+TOPIC\s+PARTITION\s+CURRENT-OFFSET\s+LOG-END-OFFSET\s+LAG\s+OWNER`),
		regexp.MustCompile(`.+\s+(?P<topic>[a-zA-Z0-9\\._\\-]+)\s+(?P<partitionId>\d+)\s+(?P<currentOffset>\d+)\s+\d+\s+(?P<lag>\d+)\s+(?P<clientId>\S+)_/(?P<consumerAddress>.+)`),
	)
	kafka0_9_0_1DescribeGroupParser = mustBuildNewRegexpParser(
		regexp.MustCompile("GROUP, TOPIC, PARTITION, CURRENT OFFSET, LOG END OFFSET, LAG, OWNER"),
		regexp.MustCompile(`[^,]+, (?P<topic>[a-zA-Z0-9\\._\\-]+), (?P<partitionId>\d+), (?P<currentOffset>\d+), \d+, (?P<lag>\d+), (?P<clientId>.+)_/(?P<consumerAddress>.+)`),
	)
)

func mustBuildNewRegexpParser(header, line *regexp.Regexp) *regexpParser {
	parser, err := newRegexpParser(header, line)
	if err != nil {
		log.Fatal(err)
	}
	return parser
}

// DefaultDescribeGroupParser returns a DelegatingParser consisting of all formats known.
func DefaultDescribeGroupParser() *DelegatingParser {
	return &DelegatingParser{
		[]DescribeGroupParser{
			kafka0_9_0_1DescribeGroupParser,
			kafka0_10_0_1DescribeGroupParser,
			kafka0_10_1DescribeGroupParser,
			kafka0_10_2_1DescribeGroupParser,
		},
	}
}

// DelegatingParser is a parser that tries multiple parser returning the first
// succesful parsed result.
type DelegatingParser struct {
	Parsers []DescribeGroupParser
}

// Parse parses the output. It tries each Parser in order, returning an error
// if all fails.
func (p *DelegatingParser) Parse(output CommandOutput) ([]exporter.PartitionInfo, error) {
	var err error
	var partitions []exporter.PartitionInfo

	for _, parser := range p.Parsers {
		partitions, err = parser.Parse(output)
		if err == nil {
			return partitions, err
		}
	}

	return nil, errors.New("no parser could parse the output")
}

func (p *DelegatingParser) String() string {
	return "DefaultParser"
}
<|endoftext|>


<commit_before>package xmlrpc

import (
	"bytes"
	"fmt"
	"reflect"
	"strings"
	"testing"
	"time"
	"encoding/xml"
)

func xmlEscapeString(src string) string {
	buf := bytes.NewBufferString("")
    xml.EscapeText(buf, []byte(src))
    return buf.String()
}

func getTypeString(val interface{}, noSpaces bool) string {
	preSpace := "\n		"
	postSpace := "\n	  "

	var pre, post string
	if noSpaces {
		pre = ""
		post = ""
	} else {
		pre = preSpace
		post = postSpace
	}

	if val == nil {
		return pre + "<nil/>" + post
	}

	switch v := val.(type) {
	case bool:
		var bVal int
		if v {
			bVal = 1
		} else {
			bVal = 0
		}
		return fmt.Sprintf("%s<boolean>%d</boolean>%s", pre, bVal, post)
	case float64:
		// hack to make float values match
		fStr := fmt.Sprintf("%f", v)
		fLen := len(fStr)
		fSub := fStr[fLen-3 : fLen]
		if fLen > 3 && fSub != "000" {
			fStr += "000"
		}

		return fmt.Sprintf("%s<double>%s</double>%s", pre, fStr, post)
	case int:
		return fmt.Sprintf("%s<int>%d</int>%s", pre, v, post)
    case []byte:
        return string(v)
	case string:
		//return v
		return fmt.Sprintf("%s<string>%s</string>%s", pre, xmlEscapeString(val.(string)), post)
	case (map[string]interface{}):
		valStr := fmt.Sprintf("%s<struct>", preSpace)
		for mkey, mval := range v {
			valStr += fmt.Sprintf(`
		  <member>
			<name>%s</name>
			<value>%v</value>
		  </member>`, mkey, getTypeString(mval, true))
		}
		valStr += fmt.Sprintf("%s</struct>%s", preSpace, postSpace)
		return valStr
	case time.Time:
		tag := "dateTime.iso8601"
		return fmt.Sprintf("%s<%s>%s</%s>%s", pre, tag,
			v.Format(ISO8601_LAYOUT), tag, post)
	}

	rval := reflect.ValueOf(val)
	if rval.Kind() == reflect.Array || rval.Kind() == reflect.Slice {
		buf := bytes.NewBufferString("\n		<array><data>\n")
		for i := 0; i < rval.Len(); i++ {
			buf.WriteString("<value>")
			wrapValue(buf, rval.Index(i))
			buf.WriteString("</value>\n")
		}
		buf.WriteString("</data></array>\n	  ")
		return buf.String()
	} else {
		fmt.Printf("Not handling Kind %v\n", rval.Kind())
	}

	return fmt.Sprintf("<???>%v(%T)</???>", val, val)
}

// Translate a local data object into an XML string
func marshalString(methodName string, args ...interface{}) (string, error) {
	buf := bytes.NewBufferString("")
	err := marshalArray(buf, methodName, args)
	if err != nil {
		return "", err
	}

	return buf.String(), nil
}

func parseAndCheck(t *testing.T, methodName string, expVal interface{},
	xmlStr string) {
	name, val, err, fault := UnmarshalString(xmlStr)
    if val != nil {
        val = extractParams(val.([]interface{}))
    }
	if err != nil {
		t.Fatalf("Returned error %s", err)
	} else if fault != nil {
		t.Fatalf("Returned fault %s", fault)
	}

	if name != methodName {
		if methodName == "" {
			t.Fatal("Did not expect method name \"%s\"", name)
		} else {
			t.Fatal("Expected method name \"%s\", not \"%s\"", methodName, name)
		}
	}

	if expVal == nil {
		if val != nil {
			t.Fatalf("Got unexpected value %v <%T>", val, val)
		}
	} else {
		if reflect.TypeOf(val) != reflect.TypeOf(expVal) {
			t.Fatalf("Returned type %T, not %T", val, expVal)
		}

		if !reflect.DeepEqual(val, expVal) {
			t.Fatalf("Returned value %v, not %v", val, expVal)
		}
	}
}

func parseUnimplemented(t *testing.T, methodName string, expVal interface{}) {
	xmlStr := wrapMethod(methodName, expVal)
	name, val, err, fault := UnmarshalString(xmlStr)
    if val != nil {
        val = extractParams(val.([]interface{}))
    }
	if err == nil {
		t.Fatalf("Unimplemented type didn't return an error")
	} else if !strings.Contains(err.Error(), "nimplemented") {
		t.Fatalf("Returned unexpected error %s", err)
	}

	if fault != nil {
		t.Fatalf("Returned unexpected fault %s", fault)
	}

	if name != methodName {
		if methodName == "" {
			t.Fatal("Did not expect method name \"%s\"", name)
		} else {
			t.Fatal("Expected method name \"%s\", not \"%s\"", methodName, name)
		}
	}

	if val != nil {
		t.Fatalf("Got value %v from unimplemented type", val)
	}
}

func wrapAndParse(t *testing.T, methodName string, expVal interface{}) {
	xmlStr := wrapMethod(methodName, expVal)
	parseAndCheck(t, methodName, expVal, xmlStr)
}

func wrapMethod(methodName string, args ...interface{}) string {
	buf := bytes.NewBufferString("<?xml version=\"1.0\"?>\n")

	var backStr string
	if methodName == "" {
		fmt.Fprintf(buf, "<methodResponse>\n")
		backStr = "</methodResponse>"
	} else {
		fmt.Fprintf(buf, "<methodCall>\n  <methodName>%s</methodName>\n",
			methodName)
		backStr = "</methodCall>"
	}

	fmt.Fprintf(buf, "  <params>\n")
	for _, a := range args {
		fmt.Fprintf(buf, `	<param>
	  <value>%v</value>
	</param>
`, getTypeString(a, false))
	}
	fmt.Fprintf(buf, "  </params>\n%s\n", backStr)

	return string(buf.Bytes())
}

func TestMakeRequestBool(t *testing.T) {
	expVal := true
	methodName := "foo"

	xmlStr, err := marshalString(methodName, expVal)
	if err != nil {
		t.Fatalf("Returned error %s", err)
	}

	expStr := wrapMethod(methodName, expVal)
	if xmlStr != expStr {
		t.Fatalf("Returned \"%s\", not \"%s\"", xmlStr, expStr)
	}
}

func TestMakeRequestDateTime(t *testing.T) {
	val := "19980717T14:08:55"
	expVal, err := time.Parse(ISO8601_LAYOUT, val)
	if err != nil {
		t.Errorf("Cannot create ISO8601 time \"%s\"\n", val)
	}

	methodName := "foo"

	xmlStr, err := marshalString(methodName, expVal)
	if err != nil {
		t.Fatalf("Returned error %s", err)
	}

	expStr := wrapMethod(methodName, expVal)
	if xmlStr != expStr {
		t.Fatalf("Returned \"%s\", not \"%s\"", xmlStr, expStr)
	}
}

func TestMakeRequestInt(t *testing.T) {
	expVal := 123456
	methodName := "foo"

	xmlStr, err := marshalString(methodName, expVal)
	if err != nil {
		t.Fatalf("Returned error %s", err)
	}

	expStr := wrapMethod(methodName, expVal)
	if xmlStr != expStr {
		t.Fatalf("Returned \"%s\", not \"%s\"", xmlStr, expStr)
	}
}

func TestMakeRequestString(t *testing.T) {
	expVal := "abcd<>?1234"
	methodName := "foo"

	xmlStr, err := marshalString(methodName, expVal)
	if err != nil {
		t.Fatalf("Returned error %s", err)
	}

	expStr := wrapMethod(methodName, expVal)
	if xmlStr != expStr {
		t.Fatalf("Returned \"%s\", not \"%s\"", xmlStr, expStr)
	}
}

func TestMakeRequestArray(t *testing.T) {
	expVal := []int{1, 2, 3, 4}
	methodName := "foo"

	xmlStr, err := marshalString(methodName, expVal)
	if err != nil {
		t.Fatalf("Returned error %s", err)
	}

	expStr := wrapMethod(methodName, expVal)
	if xmlStr != expStr {
		t.Fatalf("Returned \"%s\", not \"%s\"", xmlStr, expStr)
	}
}

func TestMakeRequestNil(t *testing.T) {
	var expVal interface{} = nil
	methodName := "foo"

	xmlStr, err := marshalString(methodName, expVal)
	if err != nil {
		t.Fatalf("Returned error %s", err)
	}

	expStr := wrapMethod(methodName, expVal)
	if xmlStr != expStr {
		t.Fatalf("Returned \"%s\", not \"%s\"", xmlStr, expStr)
	}
}

func TestMakeRequestNoData(t *testing.T) {
	methodName := "foo"

	xmlStr, err := marshalString(methodName)
	if err != nil {
		t.Fatalf("Returned error %s", err)
	}

	expStr := fmt.Sprintf(`<?xml version="1.0"?>
<methodCall>
  <methodName>%s</methodName>
  <params>
  </params>
</methodCall>
`, methodName)

	if xmlStr != expStr {
		t.Fatalf("Returned \"%s\", not \"%s\"", xmlStr, expStr)
	}
}

func TestParseRequestInt(t *testing.T) {
	wrapAndParse(t, "foo", 54321)
}

func XXXTestParseResponseArray(t *testing.T) {
	var array = []int{1, -1, 0, 1234567}
	wrapAndParse(t, "", array)
}

func TestParseResponseBase64(t *testing.T) {
	tnm := "base64"
	val := "eW91IGNhbid0IHJlYWQgdGhpcyE"
	parseUnimplemented(t, "", []byte(fmt.Sprintf("<%s>%v</%s>", tnm, val, tnm)))
}

func TestParseResponseBool(t *testing.T) {
	const expVal = true

	xmlStr := wrapMethod("", expVal)

	parseAndCheck(t, "", expVal, xmlStr)
}

func TestParseResponseDatetime(t *testing.T) {
	val := "19980717T14:08:55"
	expVal, err := time.Parse(ISO8601_LAYOUT, val)
	if err != nil {
		t.Errorf("Cannot create ISO8601 time \"%s\"\n", val)
	}

	wrapAndParse(t, "", expVal)
}

func TestParseResponseDouble(t *testing.T) {
	wrapAndParse(t, "", 123.456)
}

func TestParseResponseFault(t *testing.T) {
	code := 1
	msg := "Some fault"
	xmlStr := fmt.Sprintf(`<?xml version="1.0"?>
<methodResponse>
  <fault>
	<value>
		<struct>
		  <member>
			<name>faultCode</name>
			<value><int>%d</int></value>
		  </member>
		  <member>
			<name>faultString</name>
			<value>%s</value>
		  </member>
		</struct>
	</value>
  </fault>
</methodResponse>`, code, msg)

	name, _, err, fault := UnmarshalString(xmlStr)
	if name != "" {
		t.Fatalf("Returned name %s", name)
	} else if err != nil {
		t.Fatalf("Returned error %s", err)
	}

	if fault == nil {
		t.Fatalf("No fault was returned")
	} else if fault.Code != code {
		t.Fatalf("Expected fault code %d, not %d", code, fault.Code)
	} else if fault.Msg != msg {
		t.Fatalf("Expected fault message %s, not %s", msg, fault.Msg)
	}
}

func TestParseResponseInt(t *testing.T) {
	wrapAndParse(t, "", 1279905716)
}

func TestParseResponseI4(t *testing.T) {
	tnm := "i4"
	val := -433221

	xmlStr := wrapMethod("", []byte(fmt.Sprintf("<%s>%v</%s>", tnm, val, tnm)))
	parseAndCheck(t, "", val, xmlStr)
}

func TestParseResponseNil(t *testing.T) {
	wrapAndParse(t, "", nil)
}

func TestParseResponseNoData(t *testing.T) {
	xmlStr := `<?xml version="1.0"?>
<methodResponse>
  <params>
  </params>
</methodResponse>`

	parseAndCheck(t, "", nil, xmlStr)
}

func TestParseResponseString(t *testing.T) {
	wrapAndParse(t, "", "abc123")
}

func TestParseResponseStringEmpty(t *testing.T) {
	wrapAndParse(t, "", "")
}

func TestParseResponseStringRaw(t *testing.T) {
	const expVal = "abc123"

	xmlStr := fmt.Sprintf(`<?xml version='1.0'?>
<methodResponse>
  <params>
	<param>
	  <value>%s</value>
	</param>
  </params>
</methodResponse>`, expVal)

	parseAndCheck(t, "", expVal, xmlStr)
}

func TestParseResponseStringRawEmpty(t *testing.T) {
	xmlStr := `<?xml version='1.0'?>
<methodResponse>
  <params>
	<param>
	  <value></value>
	</param>
  </params>
</methodResponse>`

	parseAndCheck(t, "", "", xmlStr)
}

func TestParseResponseStringEscapedChars(t *testing.T) {
	xmlStr := `<?xml version='1.0'?>
<methodResponse>
  <params>
	<param>
	  <value></value></value>
	</param>
  </params>
</methodResponse>`

	parseAndCheck(t, "", "</value>", xmlStr)
}

func TestParseResponseStruct(t *testing.T) {
	structMap := map[string]interface{}{
		"boolVal": true, "intVal": 18, "strVal": "foo",
	}
	wrapAndParse(t, "", structMap)
}

func TestNewFault(tst *testing.T) {
    f := NewFault(123, "fault 123")
    tst.Logf("fault = %v", f.String())
    f = nil
    if f.String() != "NilFault" {
        tst.Errorf("miss NilFault")
    }
}
<commit_msg>testcase<commit_after>package xmlrpc

import (
	"bytes"
	"fmt"
	"reflect"
	"strings"
	"testing"
	"time"
	"encoding/xml"
)

func xmlEscapeString(src string) string {
	buf := bytes.NewBufferString("")
    xml.EscapeText(buf, []byte(src))
    return buf.String()
}

func getTypeString(val interface{}, noSpaces bool) string {
	preSpace := "\n		"
	postSpace := "\n	  "

	var pre, post string
	if noSpaces {
		pre = ""
		post = ""
	} else {
		pre = preSpace
		post = postSpace
	}

	if val == nil {
		return pre + "<nil/>" + post
	}

	switch v := val.(type) {
	case bool:
		var bVal int
		if v {
			bVal = 1
		} else {
			bVal = 0
		}
		return fmt.Sprintf("%s<boolean>%d</boolean>%s", pre, bVal, post)
	case float64:
		// hack to make float values match
		fStr := fmt.Sprintf("%f", v)
		fLen := len(fStr)
		fSub := fStr[fLen-3 : fLen]
		if fLen > 3 && fSub != "000" {
			fStr += "000"
		}

		return fmt.Sprintf("%s<double>%s</double>%s", pre, fStr, post)
	case int:
		return fmt.Sprintf("%s<int>%d</int>%s", pre, v, post)
    case []byte:
        return string(v)
	case string:
		//return v
		return fmt.Sprintf("%s<string>%s</string>%s", pre, xmlEscapeString(val.(string)), post)
	case (map[string]interface{}):
		valStr := fmt.Sprintf("%s<struct>", preSpace)
		for mkey, mval := range v {
			valStr += fmt.Sprintf(`
		  <member>
			<name>%s</name>
			<value>%v</value>
		  </member>`, mkey, getTypeString(mval, true))
		}
		valStr += fmt.Sprintf("%s</struct>%s", preSpace, postSpace)
		return valStr
	case time.Time:
		tag := "dateTime.iso8601"
		return fmt.Sprintf("%s<%s>%s</%s>%s", pre, tag,
			v.Format(ISO8601_LAYOUT), tag, post)
	}

	rval := reflect.ValueOf(val)
	if rval.Kind() == reflect.Array || rval.Kind() == reflect.Slice {
		buf := bytes.NewBufferString("\n		<array><data>\n")
		for i := 0; i < rval.Len(); i++ {
			buf.WriteString("<value>")
			wrapValue(buf, rval.Index(i))
			buf.WriteString("</value>\n")
		}
		buf.WriteString("</data></array>\n	  ")
		return buf.String()
	} else {
		fmt.Printf("Not handling Kind %v\n", rval.Kind())
	}

	return fmt.Sprintf("<???>%v(%T)</???>", val, val)
}

// Translate a local data object into an XML string
func marshalString(methodName string, args ...interface{}) (string, error) {
	buf := bytes.NewBufferString("")
	err := marshalArray(buf, methodName, args)
	if err != nil {
		return "", err
	}

	return buf.String(), nil
}

func parseAndCheck(t *testing.T, methodName string, expVal interface{},
	xmlStr string) {
	name, val, err, fault := UnmarshalString(xmlStr)
    if val != nil {
        val = extractParams(val.([]interface{}))
    }
	if err != nil {
		t.Fatalf("Returned error %s", err)
	} else if fault != nil {
		t.Fatalf("Returned fault %s", fault)
	}

	if name != methodName {
		if methodName == "" {
			t.Fatal("Did not expect method name \"%s\"", name)
		} else {
			t.Fatal("Expected method name \"%s\", not \"%s\"", methodName, name)
		}
	}

	if expVal == nil {
		if val != nil {
			t.Fatalf("Got unexpected value %v <%T>", val, val)
		}
	} else {
		if reflect.TypeOf(val) != reflect.TypeOf(expVal) {
			t.Fatalf("Returned type %T, not %T", val, expVal)
		}

		if !reflect.DeepEqual(val, expVal) {
			t.Fatalf("Returned value %v, not %v", val, expVal)
		}
	}
}

func parseUnimplemented(t *testing.T, methodName string, expVal interface{}) {
	xmlStr := wrapMethod(methodName, expVal)
	name, val, err, fault := UnmarshalString(xmlStr)
    if val != nil {
        val = extractParams(val.([]interface{}))
    }
	if err == nil {
		t.Fatalf("Unimplemented type didn't return an error")
	} else if !strings.Contains(err.Error(), "nimplemented") {
		t.Fatalf("Returned unexpected error %s", err)
	}

	if fault != nil {
		t.Fatalf("Returned unexpected fault %s", fault)
	}

	if name != methodName {
		if methodName == "" {
			t.Fatal("Did not expect method name \"%s\"", name)
		} else {
			t.Fatal("Expected method name \"%s\", not \"%s\"", methodName, name)
		}
	}

	if val != nil {
		t.Fatalf("Got value %v from unimplemented type", val)
	}
}

func wrapAndParse(t *testing.T, methodName string, expVal interface{}) {
	xmlStr := wrapMethod(methodName, expVal)
	parseAndCheck(t, methodName, expVal, xmlStr)
}

func wrapMethod(methodName string, args ...interface{}) string {
	buf := bytes.NewBufferString("<?xml version=\"1.0\"?>\n")

	var backStr string
	if methodName == "" {
		fmt.Fprintf(buf, "<methodResponse>\n")
		backStr = "</methodResponse>"
	} else {
		fmt.Fprintf(buf, "<methodCall>\n  <methodName>%s</methodName>\n",
			methodName)
		backStr = "</methodCall>"
	}

	fmt.Fprintf(buf, "  <params>\n")
	for _, a := range args {
		fmt.Fprintf(buf, `	<param>
	  <value>%v</value>
	</param>
`, getTypeString(a, false))
	}
	fmt.Fprintf(buf, "  </params>\n%s\n", backStr)

	return string(buf.Bytes())
}

func TestMakeRequestBool(t *testing.T) {
	expVal := true
	methodName := "foo"

	xmlStr, err := marshalString(methodName, expVal)
	if err != nil {
		t.Fatalf("Returned error %s", err)
	}

	expStr := wrapMethod(methodName, expVal)
	if xmlStr != expStr {
		t.Fatalf("Returned \"%s\", not \"%s\"", xmlStr, expStr)
	}
}

func TestMakeRequestDateTime(t *testing.T) {
	val := "19980717T14:08:55"
	expVal, err := time.Parse(ISO8601_LAYOUT, val)
	if err != nil {
		t.Errorf("Cannot create ISO8601 time \"%s\"\n", val)
	}

	methodName := "foo"

	xmlStr, err := marshalString(methodName, expVal)
	if err != nil {
		t.Fatalf("Returned error %s", err)
	}

	expStr := wrapMethod(methodName, expVal)
	if xmlStr != expStr {
		t.Fatalf("Returned \"%s\", not \"%s\"", xmlStr, expStr)
	}
}

func TestMakeRequestInt(t *testing.T) {
	expVal := 123456
	methodName := "foo"

	xmlStr, err := marshalString(methodName, expVal)
	if err != nil {
		t.Fatalf("Returned error %s", err)
	}

	expStr := wrapMethod(methodName, expVal)
	if xmlStr != expStr {
		t.Fatalf("Returned \"%s\", not \"%s\"", xmlStr, expStr)
	}
}

func TestMakeRequestString(t *testing.T) {
	expVal := "abcd<>?1234"
	methodName := "foo"

	xmlStr, err := marshalString(methodName, expVal)
	if err != nil {
		t.Fatalf("Returned error %s", err)
	}

	expStr := wrapMethod(methodName, expVal)
	if xmlStr != expStr {
		t.Fatalf("Returned \"%s\", not \"%s\"", xmlStr, expStr)
	}
}

func TestMakeRequestArray(t *testing.T) {
	expVal := []int{1, 2, 3, 4}
	methodName := "foo"

	xmlStr, err := marshalString(methodName, expVal)
	if err != nil {
		t.Fatalf("Returned error %s", err)
	}

	expStr := wrapMethod(methodName, expVal)
	if xmlStr != expStr {
		t.Fatalf("Returned \"%s\", not \"%s\"", xmlStr, expStr)
	}
}

func TestMakeRequestNil(t *testing.T) {
	var expVal interface{} = nil
	methodName := "foo"

	xmlStr, err := marshalString(methodName, expVal)
	if err != nil {
		t.Fatalf("Returned error %s", err)
	}

	expStr := wrapMethod(methodName, expVal)
	if xmlStr != expStr {
		t.Fatalf("Returned \"%s\", not \"%s\"", xmlStr, expStr)
	}
}

func TestMakeRequestNoData(t *testing.T) {
	methodName := "foo"

	xmlStr, err := marshalString(methodName)
	if err != nil {
		t.Fatalf("Returned error %s", err)
	}

	expStr := fmt.Sprintf(`<?xml version="1.0"?>
<methodCall>
  <methodName>%s</methodName>
  <params>
  </params>
</methodCall>
`, methodName)

	if xmlStr != expStr {
		t.Fatalf("Returned \"%s\", not \"%s\"", xmlStr, expStr)
	}
}

func TestParseRequestInt(t *testing.T) {
	wrapAndParse(t, "foo", 54321)
}

func XXXTestParseResponseArray(t *testing.T) {
	var array = []int{1, -1, 0, 1234567}
	wrapAndParse(t, "", array)
}

func TestParseResponseBase64(t *testing.T) {
	tnm := "base64"
	val := "eW91IGNhbid0IHJlYWQgdGhpcyE"
	parseUnimplemented(t, "", []byte(fmt.Sprintf("<%s>%v</%s>", tnm, val, tnm)))
}

func TestParseResponseBool(t *testing.T) {
	const expVal = true

	xmlStr := wrapMethod("", expVal)

	parseAndCheck(t, "", expVal, xmlStr)
}

func TestParseResponseDatetime(t *testing.T) {
	val := "19980717T14:08:55"
	expVal, err := time.Parse(ISO8601_LAYOUT, val)
	if err != nil {
		t.Errorf("Cannot create ISO8601 time \"%s\"\n", val)
	}

	wrapAndParse(t, "", expVal)
}

func TestParseResponseDouble(t *testing.T) {
	wrapAndParse(t, "", 123.456)
}

func TestParseResponseFault(t *testing.T) {
	code := 1
	msg := "Some fault"
	xmlStr := fmt.Sprintf(`<?xml version="1.0"?>
<methodResponse>
  <fault>
	<value>
		<struct>
		  <member>
			<name>faultCode</name>
			<value><int>%d</int></value>
		  </member>
		  <member>
			<name>faultString</name>
			<value>%s</value>
		  </member>
		</struct>
	</value>
  </fault>
</methodResponse>`, code, msg)

	name, _, err, fault := UnmarshalString(xmlStr)
	if name != "" {
		t.Fatalf("Returned name %s", name)
	} else if err != nil {
		t.Fatalf("Returned error %s", err)
	}

	if fault == nil {
		t.Fatalf("No fault was returned")
	} else if fault.Code != code {
		t.Fatalf("Expected fault code %d, not %d", code, fault.Code)
	} else if fault.Msg != msg {
		t.Fatalf("Expected fault message %s, not %s", msg, fault.Msg)
	}
}

func TestParseResponseInt(t *testing.T) {
	wrapAndParse(t, "", 1279905716)
}

func TestParseResponseI4(t *testing.T) {
	tnm := "i4"
	val := -433221

	xmlStr := wrapMethod("", []byte(fmt.Sprintf("<%s>%v</%s>", tnm, val, tnm)))
	parseAndCheck(t, "", val, xmlStr)
}

func TestParseResponseNil(t *testing.T) {
	wrapAndParse(t, "", nil)
}

func TestParseResponseNoData(t *testing.T) {
	xmlStr := `<?xml version="1.0"?>
<methodResponse>
  <params>
  </params>
</methodResponse>`

	parseAndCheck(t, "", nil, xmlStr)
}

func TestParseResponseString(t *testing.T) {
	wrapAndParse(t, "", "abc123")
}

func TestParseResponseStringEmpty(t *testing.T) {
	wrapAndParse(t, "", "")
}

func TestParseResponseStringRaw(t *testing.T) {
	const expVal = "abc123"

	xmlStr := fmt.Sprintf(`<?xml version='1.0'?>
<methodResponse>
  <params>
	<param>
	  <value>%s</value>
	</param>
  </params>
</methodResponse>`, expVal)

	parseAndCheck(t, "", expVal, xmlStr)
}

func TestParseResponseStringRawEmpty(t *testing.T) {
	xmlStr := `<?xml version='1.0'?>
<methodResponse>
  <params>
	<param>
	  <value></value>
	</param>
  </params>
</methodResponse>`

	parseAndCheck(t, "", "", xmlStr)
}

func TestParseResponseStringEscapedChars(t *testing.T) {
	xmlStr := `<?xml version='1.0'?>
<methodResponse>
  <params>
	<param>
	  <value></value></value>
	</param>
  </params>
</methodResponse>`

	parseAndCheck(t, "", "</value>", xmlStr)
}

func TestParseResponseStruct(t *testing.T) {
	structMap := map[string]interface{}{
		"boolVal": true, "intVal": 18, "strVal": "foo",
	}
	wrapAndParse(t, "", structMap)
}

func TestNewFault(tst *testing.T) {
    f := NewFault(123, "fault 123")
    tst.Logf("fault = %v", f.String())
    f = nil
    if f.String() != "NilFault" {
        tst.Errorf("miss NilFault")
    }
}

func TestUnmarshal(tst *testing.T) {
    r := bytes.NewBufferString("abc</methodName>")
    s, i, e, f := Unmarshal(r)
    tst.Logf("%v %v %v %v", s, i, e, f)
    r = nil
    //Unmarshal(r)
}
<|endoftext|>


<commit_before>package lib

import (
	"fmt"
	"net/url"
	"os"
	"os/user"
	"strings"
)

// SchemePrefix is the prefix of oss url
const SchemePrefix string = "oss://"

type CloudURLType string

const (
	CloudURLNone    CloudURLType = "none"
	CloudURLService CloudURLType = "service"
	CloudURLBucket  CloudURLType = "bucket"
	CloudURLObject  CloudURLType = "object"
)

// StorageURLer is the interface for all url
type StorageURLer interface {
	IsCloudURL() bool
	IsFileURL() bool
	ToString() string
}

// CloudURL describes oss url
type CloudURL struct {
	urlStr string
	bucket string
	object string
}

// Init is used to create a cloud url from a user input url
func (cu *CloudURL) Init(urlStr, encodingType string) error {
	cu.urlStr = urlStr
	if err := cu.parseBucketObject(encodingType); err != nil {
		return err
	}
	if err := cu.checkBucketObject(encodingType); err != nil {
		return err
	}
	return nil
}

func (cu *CloudURL) parseBucketObject(encodingType string) error {
	var err error
	path := cu.urlStr

	if strings.HasPrefix(strings.ToLower(path), SchemePrefix) {
		path = string(path[len(SchemePrefix):])
	} else {
		// deal with the url: /bucket/object
		if strings.HasPrefix(path, "/") {
			path = string(path[1:])
		}
	}

	sli := strings.SplitN(path, "/", 2)
	cu.bucket = sli[0]
	if len(sli) > 1 {
		cu.object = sli[1]
		if encodingType == URLEncodingType {
			if cu.object, err = url.QueryUnescape(cu.object); err != nil {
				return fmt.Errorf("invalid cloud url: %s, object name is not url encoded, %s", cu.urlStr, err.Error())
			}
		}
	}
	return nil
}

func (cu *CloudURL) checkBucketObject(encodingType string) error {
	if cu.bucket == "" && cu.object != "" {
		return fmt.Errorf("invalid cloud url: %s, miss bucket", cu.urlStr)
	}
	if encodingType == URLEncodingType && cu.bucket != "" && cu.object == "" {
		if bucket, err := url.QueryUnescape(cu.bucket); err == nil && bucket != cu.bucket {
			return fmt.Errorf("invalid cloud url: %s, bucket url do not support --encoding-type option", cu.urlStr)
		}
	}
	return nil
}

func (cu *CloudURL) checkObjectPrefix() error {
	if strings.HasPrefix(cu.object, "/") {
		return fmt.Errorf("invalid cloud url: %s, object name should not begin with \"/\"", cu.urlStr)
	}
	if strings.HasPrefix(cu.object, "\\") {
		return fmt.Errorf("invalid cloud url: %s, object name should not begin with \"\\\"", cu.urlStr)
	}
	return nil
}

func (cu *CloudURL) checkIsObjectURL() error {
	if cu.bucket == "" {
		return fmt.Errorf("invalid cloud url: %s, miss bucket", cu.urlStr)
	}
	if cu.object == "" {
		return fmt.Errorf("invalid cloud url: %s, miss object", cu.urlStr)
	}
	return nil
}

// IsCloudURL shows if the url is a cloud url
func (cu CloudURL) IsCloudURL() bool {
	return true
}

// IsFileURL shows if the url is a file url
func (cu CloudURL) IsFileURL() bool {
	return false
}

// ToString reconstruct url
func (cu CloudURL) ToString() string {
	if cu.object == "" {
		return fmt.Sprintf("%s%s", SchemePrefix, cu.bucket)
	}
	return fmt.Sprintf("%s%s/%s", SchemePrefix, cu.bucket, cu.object)
}

// FileURL describes file url
type FileURL struct {
	urlStr string
}

// Init simulate inheritance, and polymorphism
func (fu *FileURL) Init(urlStr, encodingType string) error {
	if encodingType == URLEncodingType {
		vurl, err := url.QueryUnescape(urlStr)
		if err != nil {
			return fmt.Errorf("invalid cloud url: %s, file name is not url encoded, %s", urlStr, err.Error())
		}
		urlStr = vurl
	}

	usr, _ := user.Current()
	dir := usr.HomeDir
	if len(urlStr) >= 2 && urlStr[:2] == "~"+string(os.PathSeparator) {
		urlStr = strings.Replace(urlStr, "~", dir, 1)
	}
	fu.urlStr = urlStr
	return nil
}

// IsCloudURL simulate inheritance, and polymorphism
func (fu FileURL) IsCloudURL() bool {
	return false
}

// IsFileURL simulate inheritance, and polymorphism
func (fu FileURL) IsFileURL() bool {
	return true
}

// ToString simulate inheritance, and polymorphism
func (fu FileURL) ToString() string {
	return fu.urlStr
}

// StorageURLFromString analysis input url type and build a storage url from the url
func StorageURLFromString(urlStr, encodingType string) (StorageURLer, error) {
	if strings.HasPrefix(strings.ToLower(urlStr), SchemePrefix) {
		var cloudURL CloudURL
		if err := cloudURL.Init(urlStr, encodingType); err != nil {
			return nil, err
		}
		return cloudURL, nil
	}
	var fileURL FileURL
	if err := fileURL.Init(urlStr, encodingType); err != nil {
		return nil, err
	}
	return fileURL, nil
}

// CloudURLFromString get a oss url from url, if url is not a cloud url, return error
func CloudURLFromString(urlStr, encodingType string) (CloudURL, error) {
	storageURL, err := StorageURLFromString(urlStr, encodingType)
	if err != nil {
		return CloudURL{}, err
	}
	if !storageURL.IsCloudURL() {
		return CloudURL{}, fmt.Errorf("invalid cloud url: \"%s\", please make sure the url starts with: \"%s\"", urlStr, SchemePrefix)
	}
	return storageURL.(CloudURL), nil
}

// ObjectURLFromString get a oss url from url, if url is not a cloud url, return error
func ObjectURLFromString(urlStr, encodingType string) (CloudURL, error) {
	cloudURL, err := CloudURLFromString(urlStr, encodingType)
	if err != nil {
		return cloudURL, err
	}
	return cloudURL, cloudURL.checkIsObjectURL()
}

// CloudURLToString format url string from input
func CloudURLToString(bucket string, object string) string {
	cloudURL := CloudURL{
		bucket: bucket,
		object: object,
	}
	return cloudURL.ToString()
}
<commit_msg>fix another user.Current() null ptr<commit_after>package lib

import (
	"fmt"
	"net/url"
	"os"
	"os/user"
	"strings"
	"path/filepath"
)

// SchemePrefix is the prefix of oss url
const SchemePrefix string = "oss://"

type CloudURLType string

const (
	CloudURLNone    CloudURLType = "none"
	CloudURLService CloudURLType = "service"
	CloudURLBucket  CloudURLType = "bucket"
	CloudURLObject  CloudURLType = "object"
)

// StorageURLer is the interface for all url
type StorageURLer interface {
	IsCloudURL() bool
	IsFileURL() bool
	ToString() string
}

// CloudURL describes oss url
type CloudURL struct {
	urlStr string
	bucket string
	object string
}

// Init is used to create a cloud url from a user input url
func (cu *CloudURL) Init(urlStr, encodingType string) error {
	cu.urlStr = urlStr
	if err := cu.parseBucketObject(encodingType); err != nil {
		return err
	}
	if err := cu.checkBucketObject(encodingType); err != nil {
		return err
	}
	return nil
}

func (cu *CloudURL) parseBucketObject(encodingType string) error {
	var err error
	path := cu.urlStr

	if strings.HasPrefix(strings.ToLower(path), SchemePrefix) {
		path = string(path[len(SchemePrefix):])
	} else {
		// deal with the url: /bucket/object
		if strings.HasPrefix(path, "/") {
			path = string(path[1:])
		}
	}

	sli := strings.SplitN(path, "/", 2)
	cu.bucket = sli[0]
	if len(sli) > 1 {
		cu.object = sli[1]
		if encodingType == URLEncodingType {
			if cu.object, err = url.QueryUnescape(cu.object); err != nil {
				return fmt.Errorf("invalid cloud url: %s, object name is not url encoded, %s", cu.urlStr, err.Error())
			}
		}
	}
	return nil
}

func (cu *CloudURL) checkBucketObject(encodingType string) error {
	if cu.bucket == "" && cu.object != "" {
		return fmt.Errorf("invalid cloud url: %s, miss bucket", cu.urlStr)
	}
	if encodingType == URLEncodingType && cu.bucket != "" && cu.object == "" {
		if bucket, err := url.QueryUnescape(cu.bucket); err == nil && bucket != cu.bucket {
			return fmt.Errorf("invalid cloud url: %s, bucket url do not support --encoding-type option", cu.urlStr)
		}
	}
	return nil
}

func (cu *CloudURL) checkObjectPrefix() error {
	if strings.HasPrefix(cu.object, "/") {
		return fmt.Errorf("invalid cloud url: %s, object name should not begin with \"/\"", cu.urlStr)
	}
	if strings.HasPrefix(cu.object, "\\") {
		return fmt.Errorf("invalid cloud url: %s, object name should not begin with \"\\\"", cu.urlStr)
	}
	return nil
}

func (cu *CloudURL) checkIsObjectURL() error {
	if cu.bucket == "" {
		return fmt.Errorf("invalid cloud url: %s, miss bucket", cu.urlStr)
	}
	if cu.object == "" {
		return fmt.Errorf("invalid cloud url: %s, miss object", cu.urlStr)
	}
	return nil
}

// IsCloudURL shows if the url is a cloud url
func (cu CloudURL) IsCloudURL() bool {
	return true
}

// IsFileURL shows if the url is a file url
func (cu CloudURL) IsFileURL() bool {
	return false
}

// ToString reconstruct url
func (cu CloudURL) ToString() string {
	if cu.object == "" {
		return fmt.Sprintf("%s%s", SchemePrefix, cu.bucket)
	}
	return fmt.Sprintf("%s%s/%s", SchemePrefix, cu.bucket, cu.object)
}

// FileURL describes file url
type FileURL struct {
	urlStr string
}

// Init simulate inheritance, and polymorphism
func (fu *FileURL) Init(urlStr, encodingType string) error {
	if encodingType == URLEncodingType {
		vurl, err := url.QueryUnescape(urlStr)
		if err != nil {
			return fmt.Errorf("invalid cloud url: %s, file name is not url encoded, %s", urlStr, err.Error())
		}
		urlStr = vurl
	}

	usr, err := user.Current()
	var dir string
	if err != nil {
		ex, derr := os.Executable()
		if derr != nil {
			dir = "/tmp"
		}
		dir = filepath.Dir(ex)
	} else {
		dir = usr.HomeDir
	}
	if len(urlStr) >= 2 && urlStr[:2] == "~"+string(os.PathSeparator) {
		urlStr = strings.Replace(urlStr, "~", dir, 1)
	}
	fu.urlStr = urlStr
	return nil
}

// IsCloudURL simulate inheritance, and polymorphism
func (fu FileURL) IsCloudURL() bool {
	return false
}

// IsFileURL simulate inheritance, and polymorphism
func (fu FileURL) IsFileURL() bool {
	return true
}

// ToString simulate inheritance, and polymorphism
func (fu FileURL) ToString() string {
	return fu.urlStr
}

// StorageURLFromString analysis input url type and build a storage url from the url
func StorageURLFromString(urlStr, encodingType string) (StorageURLer, error) {
	if strings.HasPrefix(strings.ToLower(urlStr), SchemePrefix) {
		var cloudURL CloudURL
		if err := cloudURL.Init(urlStr, encodingType); err != nil {
			return nil, err
		}
		return cloudURL, nil
	}
	var fileURL FileURL
	if err := fileURL.Init(urlStr, encodingType); err != nil {
		return nil, err
	}
	return fileURL, nil
}

// CloudURLFromString get a oss url from url, if url is not a cloud url, return error
func CloudURLFromString(urlStr, encodingType string) (CloudURL, error) {
	storageURL, err := StorageURLFromString(urlStr, encodingType)
	if err != nil {
		return CloudURL{}, err
	}
	if !storageURL.IsCloudURL() {
		return CloudURL{}, fmt.Errorf("invalid cloud url: \"%s\", please make sure the url starts with: \"%s\"", urlStr, SchemePrefix)
	}
	return storageURL.(CloudURL), nil
}

// ObjectURLFromString get a oss url from url, if url is not a cloud url, return error
func ObjectURLFromString(urlStr, encodingType string) (CloudURL, error) {
	cloudURL, err := CloudURLFromString(urlStr, encodingType)
	if err != nil {
		return cloudURL, err
	}
	return cloudURL, cloudURL.checkIsObjectURL()
}

// CloudURLToString format url string from input
func CloudURLToString(bucket string, object string) string {
	cloudURL := CloudURL{
		bucket: bucket,
		object: object,
	}
	return cloudURL.ToString()
}
<|endoftext|>