This commit is contained in:
John
2018-12-30 11:08:07 +08:00
parent 43f21dfe92
commit e558863743
65 changed files with 1311 additions and 8931 deletions

View File

@ -1,7 +1,7 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// If a copy of the MIT was not distributed with l file,
// You can obtain one at https://gitee.com/johng/gf.
//
@ -29,77 +29,77 @@ func New(safe...bool) *List {
}
// 往链表头入栈数据项
func (this *List) PushFront(v interface{}) *list.Element {
this.mu.Lock()
e := this.list.PushFront(v)
this.mu.Unlock()
func (l *List) PushFront(v interface{}) *list.Element {
l.mu.Lock()
e := l.list.PushFront(v)
l.mu.Unlock()
return e
}
// 往链表尾入栈数据项
func (this *List) PushBack(v interface{}) *list.Element {
this.mu.Lock()
r := this.list.PushBack(v)
this.mu.Unlock()
func (l *List) PushBack(v interface{}) *list.Element {
l.mu.Lock()
r := l.list.PushBack(v)
l.mu.Unlock()
return r
}
// 在list 中元素mark之后插入一个值为v的元素并返回该元素如果mark不是list中元素则list不改变。
func (this *List) InsertAfter(v interface{}, mark *list.Element) *list.Element {
this.mu.Lock()
r := this.list.InsertAfter(v, mark)
this.mu.Unlock()
func (l *List) InsertAfter(v interface{}, mark *list.Element) *list.Element {
l.mu.Lock()
r := l.list.InsertAfter(v, mark)
l.mu.Unlock()
return r
}
// 在list 中元素mark之前插入一个值为v的元素并返回该元素如果mark不是list中元素则list不改变。
func (this *List) InsertBefore(v interface{}, mark *list.Element) *list.Element {
this.mu.Lock()
r := this.list.InsertBefore(v, mark)
this.mu.Unlock()
func (l *List) InsertBefore(v interface{}, mark *list.Element) *list.Element {
l.mu.Lock()
r := l.list.InsertBefore(v, mark)
l.mu.Unlock()
return r
}
// 批量往链表头入栈数据项
func (this *List) BatchPushFront(vs []interface{}) {
this.mu.Lock()
func (l *List) BatchPushFront(vs []interface{}) {
l.mu.Lock()
for _, item := range vs {
this.list.PushFront(item)
l.list.PushFront(item)
}
this.mu.Unlock()
l.mu.Unlock()
}
// 从链表尾端出栈数据项(删除)
func (this *List) PopBack() interface{} {
this.mu.Lock()
if elem := this.list.Back(); elem != nil {
item := this.list.Remove(elem)
this.mu.Unlock()
func (l *List) PopBack() interface{} {
l.mu.Lock()
if elem := l.list.Back(); elem != nil {
item := l.list.Remove(elem)
l.mu.Unlock()
return item
}
this.mu.Unlock()
l.mu.Unlock()
return nil
}
// 从链表头端出栈数据项(删除)
func (this *List) PopFront() interface{} {
this.mu.Lock()
if elem := this.list.Front(); elem != nil {
item := this.list.Remove(elem)
this.mu.Unlock()
func (l *List) PopFront() interface{} {
l.mu.Lock()
if elem := l.list.Front(); elem != nil {
item := l.list.Remove(elem)
l.mu.Unlock()
return item
}
this.mu.Unlock()
l.mu.Unlock()
return nil
}
// 批量从链表尾端出栈数据项(删除)
func (this *List) BatchPopBack(max int) []interface{} {
this.mu.Lock()
count := this.list.Len()
func (l *List) BatchPopBack(max int) []interface{} {
l.mu.Lock()
count := l.list.Len()
if count == 0 {
this.mu.Unlock()
l.mu.Unlock()
return []interface{}{}
}
@ -108,18 +108,18 @@ func (this *List) BatchPopBack(max int) []interface{} {
}
items := make([]interface{}, count)
for i := 0; i < count; i++ {
items[i] = this.list.Remove(this.list.Back())
items[i] = l.list.Remove(l.list.Back())
}
this.mu.Unlock()
l.mu.Unlock()
return items
}
// 批量从链表头端出栈数据项(删除)
func (this *List) BatchPopFront(max int) []interface{} {
this.mu.Lock()
count := this.list.Len()
func (l *List) BatchPopFront(max int) []interface{} {
l.mu.Lock()
count := l.list.Len()
if count == 0 {
this.mu.Unlock()
l.mu.Unlock()
return []interface{}{}
}
@ -128,137 +128,151 @@ func (this *List) BatchPopFront(max int) []interface{} {
}
items := make([]interface{}, count)
for i := 0; i < count; i++ {
items[i] = this.list.Remove(this.list.Front())
items[i] = l.list.Remove(l.list.Front())
}
this.mu.Unlock()
l.mu.Unlock()
return items
}
// 批量从链表尾端依次获取所有数据(删除)
func (this *List) PopBackAll() []interface{} {
this.mu.Lock()
count := this.list.Len()
func (l *List) PopBackAll() []interface{} {
l.mu.Lock()
count := l.list.Len()
if count == 0 {
this.mu.Unlock()
l.mu.Unlock()
return []interface{}{}
}
items := make([]interface{}, count)
for i := 0; i < count; i++ {
items[i] = this.list.Remove(this.list.Back())
items[i] = l.list.Remove(l.list.Back())
}
this.mu.Unlock()
l.mu.Unlock()
return items
}
// 批量从链表头端依次获取所有数据(删除)
func (this *List) PopFrontAll() []interface{} {
this.mu.Lock()
count := this.list.Len()
func (l *List) PopFrontAll() []interface{} {
l.mu.Lock()
count := l.list.Len()
if count == 0 {
this.mu.Unlock()
l.mu.Unlock()
return []interface{}{}
}
items := make([]interface{}, count)
for i := 0; i < count; i++ {
items[i] = this.list.Remove(this.list.Front())
items[i] = l.list.Remove(l.list.Front())
}
this.mu.Unlock()
l.mu.Unlock()
return items
}
// 删除数据项
func (this *List) Remove(e *list.Element) interface{} {
this.mu.Lock()
r := this.list.Remove(e)
this.mu.Unlock()
func (l *List) Remove(e *list.Element) interface{} {
l.mu.Lock()
r := l.list.Remove(e)
l.mu.Unlock()
return r
}
// 删除所有数据项
func (this *List) RemoveAll() {
this.mu.Lock()
this.list = list.New()
this.mu.Unlock()
func (l *List) RemoveAll() {
l.mu.Lock()
l.list = list.New()
l.mu.Unlock()
}
// 从链表头获取所有数据(不删除)
func (this *List) FrontAll() []interface{} {
this.mu.RLock()
count := this.list.Len()
func (l *List) FrontAll() []interface{} {
l.mu.RLock()
count := l.list.Len()
if count == 0 {
this.mu.RUnlock()
l.mu.RUnlock()
return []interface{}{}
}
items := make([]interface{}, 0, count)
for e := this.list.Front(); e != nil; e = e.Next() {
for e := l.list.Front(); e != nil; e = e.Next() {
items = append(items, e.Value)
}
this.mu.RUnlock()
l.mu.RUnlock()
return items
}
// 从链表尾获取所有数据(不删除)
func (this *List) BackAll() []interface{} {
this.mu.RLock()
count := this.list.Len()
func (l *List) BackAll() []interface{} {
l.mu.RLock()
count := l.list.Len()
if count == 0 {
this.mu.RUnlock()
l.mu.RUnlock()
return []interface{}{}
}
items := make([]interface{}, 0, count)
for e := this.list.Back(); e != nil; e = e.Prev() {
for e := l.list.Back(); e != nil; e = e.Prev() {
items = append(items, e.Value)
}
this.mu.RUnlock()
l.mu.RUnlock()
return items
}
// 获取链表头值(不删除)
func (this *List) FrontItem() interface{} {
this.mu.RLock()
if f := this.list.Front(); f != nil {
this.mu.RUnlock()
func (l *List) FrontItem() interface{} {
l.mu.RLock()
if f := l.list.Front(); f != nil {
l.mu.RUnlock()
return f.Value
}
this.mu.RUnlock()
l.mu.RUnlock()
return nil
}
// 获取链表尾值(不删除)
func (this *List) BackItem() interface{} {
this.mu.RLock()
if f := this.list.Back(); f != nil {
this.mu.RUnlock()
func (l *List) BackItem() interface{} {
l.mu.RLock()
if f := l.list.Back(); f != nil {
l.mu.RUnlock()
return f.Value
}
this.mu.RUnlock()
l.mu.RUnlock()
return nil
}
// 获取表头指针
func (this *List) Front() *list.Element {
this.mu.RLock()
r := this.list.Front()
this.mu.RUnlock()
func (l *List) Front() *list.Element {
l.mu.RLock()
r := l.list.Front()
l.mu.RUnlock()
return r
}
// 获取表位指针
func (this *List) Back() *list.Element {
this.mu.RLock()
r := this.list.Back()
this.mu.RUnlock()
func (l *List) Back() *list.Element {
l.mu.RLock()
r := l.list.Back()
l.mu.RUnlock()
return r
}
// 获取链表长度
func (this *List) Len() int {
this.mu.RLock()
length := this.list.Len()
this.mu.RUnlock()
func (l *List) Len() int {
l.mu.RLock()
length := l.list.Len()
l.mu.RUnlock()
return length
}
// 读锁操作
func (l *List) RLockFunc(f func(l *list.List)) {
l.mu.RLock()
defer l.mu.RUnlock()
f(l.list)
}
// 写锁操作
func (l *List) LockFunc(f func(l *list.List)) {
l.mu.Lock()
defer l.mu.Unlock()
f(l.list)
}

View File

@ -4,55 +4,52 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// 定时任务.
// Package gcron implements a cron pattern parser and job runner/定时任务.
package gcron
import (
"gitee.com/johng/gf/g/container/garray"
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/os/gtime"
"gitee.com/johng/gf/third/github.com/robfig/cron"
const (
MODE_NORMAL = 0
MODE_SINGLETON = 1
MODE_ONCE = 2
STATUS_READY = 0
STATUS_RUNNING = 1
STATUS_CLOSED = -1
)
// 定时任务项
type Entry struct {
Spec string // 注册定时任务时间格式
Cmd string // 注册定时任务名称
Time *gtime.Time // 注册时间
Name string // 定时任务名称
Status *gtype.Int // 定时任务状态(0: 未执行; > 0: 运行中)
cron *cron.Cron // 定时任务单独的底层定时管理对象
}
// 定时任务管理对象
type Cron struct {
cron *cron.Cron // 底层定时管理对象
entries *garray.Array // 定时任务注册项
status *gtype.Int // 默认定时任务管理对象状态(不带名称的定时任务0: 未执行; > 0: 运行中)
}
var (
// 默认的cron管理对象
defaultCron = New()
)
// 创建自定义的定时任务管理对象
func New() *Cron {
return &Cron {
cron : cron.New(),
entries : garray.New(0, 0, true),
status : gtype.NewInt(),
}
// 添加执行方法,可以给定名字,以便于后续执行删除
func Add(pattern string, job func(), name ... string) (*Entry, error) {
return defaultCron.Add(pattern, job, name...)
}
// 添加执行方法,可以给定名字,以便于后续执行删除
func Add(spec string, f func(), name ... string) error {
return defaultCron.Add(spec, f, name...)
// 添加单例运行定时任务
func AddSingleton(pattern string, job func(), name ... string) (*Entry, error) {
return defaultCron.AddSingleton(pattern, job, name...)
}
// 添加只运行一次的定时任务
func AddOnce(pattern string, job func(), name ... string) (*Entry, error) {
return defaultCron.AddOnce(pattern, job, name...)
}
// 延迟添加定时任务delay参数单位为秒
func DelayAdd(delay int, spec string, f func(), name ... string) {
defaultCron.DelayAdd(delay, spec, f, name...)
func DelayAdd(delay int, pattern string, job func(), name ... string) {
defaultCron.DelayAdd(delay, pattern, job, name...)
}
// 延迟添加单例定时任务delay参数单位为秒
func DelayAddSingleton(delay int, pattern string, job func(), name ... string) {
defaultCron.DelayAddSingleton(delay, pattern, job, name...)
}
// 延迟添加只运行一次的定时任务delay参数单位为秒
func DelayAddOnce(delay int, pattern string, job func(), name ... string) {
defaultCron.DelayAddOnce(delay, pattern, job, name...)
}
// 检索指定名称的定时任务

View File

@ -0,0 +1,20 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
package gcron_test
import (
"gitee.com/johng/gf/g/os/gcron"
"testing"
)
func Benchmark_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
gcron.Add("* * * * * *", func() {
})
}
}

View File

@ -9,91 +9,112 @@ package gcron
import (
"errors"
"fmt"
"gitee.com/johng/gf/g/container/garray"
"gitee.com/johng/gf/g/container/gmap"
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/os/gtime"
"gitee.com/johng/gf/third/github.com/robfig/cron"
"reflect"
"runtime"
"strconv"
"time"
)
// 定时任务管理对象
type Cron struct {
idgen *gtype.Int // 用于唯一名称生成
status *gtype.Int // 定时任务状态(0: 未执行; 1: 运行中; -1:删除关闭)
entries *gmap.StringInterfaceMap // 所有的定时任务项
}
// 创建自定义的定时任务管理对象
func New() *Cron {
cron := &Cron {
idgen : gtype.NewInt(1000000),
status : gtype.NewInt(STATUS_RUNNING),
entries : gmap.NewStringInterfaceMap(),
}
cron.startLoop()
return cron
}
// 添加定时任务
func (c *Cron) Add(spec string, f func(), name ... string) error {
func (c *Cron) Add(pattern string, job func(), name ... string) (*Entry, error) {
if len(name) > 0 {
if Search(name[0]) != nil {
return errors.New(fmt.Sprintf(`cron job "%s" already exists`, name[0]))
}
jobCron := cron.New()
if err := jobCron.AddFunc(spec, f); err == nil {
entry := &Entry{
Spec : spec,
Cmd : runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name(),
Time : gtime.Now(),
Name : name[0],
Status : gtype.NewInt(0),
cron : jobCron,
}
entry.Start()
c.entries.Append(entry)
} else {
return err
}
} else {
if err := c.cron.AddFunc(spec, f); err == nil {
entry := &Entry {
Spec : spec,
Cmd : runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name(),
Time : gtime.Now(),
Status : c.status,
cron : c.cron,
}
entry.Start()
c.entries.Append(entry)
} else {
return err
if c.Search(name[0]) != nil {
return nil, errors.New(fmt.Sprintf(`cron job "%s" already exists`, name[0]))
}
}
return nil
entry, err := newEntry(pattern, job, name ...)
if err != nil {
return nil, err
}
if len(name) > 0 {
entry.Name = name[0]
} else {
entry.Name = strconv.Itoa(c.idgen.Add(1))
}
c.entries.Set(entry.Name, entry)
return entry, nil
}
// 添加单例运行定时任务
func (c *Cron) AddSingleton(pattern string, job func(), name ... string) (*Entry, error) {
if entry, err := c.Add(pattern, job, name ...); err != nil {
return nil, err
} else {
entry.SetMode(MODE_SINGLETON)
return entry, nil
}
}
// 添加只运行一次的定时任务
func (c *Cron) AddOnce(pattern string, job func(), name ... string) (*Entry, error) {
if entry, err := c.Add(pattern, job, name ...); err != nil {
return nil, err
} else {
entry.SetMode(MODE_ONCE)
return entry, nil
}
}
// 延迟添加定时任务delay参数单位为秒
func (c *Cron) DelayAdd(delay int, spec string, f func(), name ... string) {
gtime.SetTimeout(time.Duration(delay)*time.Second, func() {
if err := c.Add(spec, f, name ...); err != nil {
func (c *Cron) DelayAdd(delay int, pattern string, job func(), name ... string) {
go func() {
time.Sleep(time.Duration(delay)*time.Second)
if _, err := c.Add(pattern, job, name ...); err != nil {
panic(err)
}
})
}()
}
// 延迟添加单例定时任务delay参数单位为秒
func (c *Cron) DelayAddSingleton(delay int, pattern string, job func(), name ... string) {
go func() {
time.Sleep(time.Duration(delay)*time.Second)
if _, err := c.AddSingleton(pattern, job, name ...); err != nil {
panic(err)
}
}()
}
// 延迟添加只运行一次的定时任务delay参数单位为秒
func (c *Cron) DelayAddOnce(delay int, pattern string, job func(), name ... string) {
go func() {
time.Sleep(time.Duration(delay)*time.Second)
if _, err := c.AddOnce(pattern, job, name ...); err != nil {
panic(err)
}
}()
}
// 检索指定名称的定时任务
func (c *Cron) Search(name string) *Entry {
entry, _ := c.searchEntry(name)
return entry
}
// 检索指定名称的定时任务
func (c *Cron) searchEntry(name string) (*Entry, int) {
entry := (*Entry)(nil)
index := -1
c.entries.RLockFunc(func(array []interface{}) {
for k, v := range array {
e := v.(*Entry)
if e.Name == name {
entry = e
index = k
break
}
}
})
return entry, index
if v := c.entries.Get(name); v != nil {
return v.(*Entry)
}
return nil
}
// 根据指定名称删除定时任务
func (c *Cron) Remove(name string) {
if entry, index := c.searchEntry(name); index >= 0 {
entry.cron.Stop()
c.entries.Remove(index)
}
c.entries.Remove(name)
}
// 开启定时任务执行(可以指定特定名称的一个或若干个定时任务)
@ -105,15 +126,11 @@ func (c *Cron) Start(name...string) {
}
}
} else {
c.entries.RLockFunc(func(array []interface{}) {
for _, v := range array {
v.(*Entry).Start()
}
})
c.status.Set(STATUS_RUNNING)
}
}
// 关闭定时任务执行(可以指定特定名称的一个或若干个定时任务)
// 停止定时任务执行(可以指定特定名称的一个或若干个定时任务)
func (c *Cron) Stop(name...string) {
if len(name) > 0 {
for _, v := range name {
@ -122,21 +139,35 @@ func (c *Cron) Stop(name...string) {
}
}
} else {
c.entries.RLockFunc(func(array []interface{}) {
for _, v := range array {
v.(*Entry).Stop()
}
})
c.status.Set(STATUS_READY)
}
}
// 关闭定时任务
func (c *Cron) Close() {
c.status.Set(STATUS_CLOSED)
}
// 获取所有已注册的定时任务项
// 获取所有已注册的定时任务项(按照注册时间从小到大进行排序)
func (c *Cron) Entries() []*Entry {
length := c.entries.Len()
entries := make([]*Entry, length)
for i := 0; i < length; i++ {
entries[i] = c.entries.Get(i).(*Entry)
}
array := garray.NewSortedArray(c.entries.Size(), func(v1, v2 interface{}) int {
entry1 := v1.(*Entry)
entry2 := v2.(*Entry)
if entry1.Time.Nanosecond() > entry2.Time.Nanosecond() {
return 1
}
return -1
}, false)
c.entries.RLockFunc(func(m map[string]interface{}) {
for _, v := range m {
array.Add(v.(*Entry))
}
})
entries := make([]*Entry, array.Len())
array.RLockFunc(func(array []interface{}) {
for k, v := range array {
entries[k] = v.(*Entry)
}
})
return entries
}

View File

@ -6,16 +6,60 @@
package gcron
// 启动定时任务
func (entry *Entry) Start() {
if entry.Status.Set(1) == 0 {
entry.cron.Start()
}
import (
"gitee.com/johng/gf/g/container/gtype"
"reflect"
"runtime"
"time"
)
// 定时任务项
type Entry struct {
mode *gtype.Int // 任务运行模式(0: normal; 1: singleton; 2: once)
status *gtype.Int // 定时任务状态(0: ready; 1: running; -1: stopped)
schedule *cronSchedule // 定时任务配置对象
Name string // 定时任务名称
Job func() // 注册定时任务方法
JobName string // 注册定时任务名称
Time time.Time // 注册时间
}
// 关闭定时任务
func (entry *Entry) Stop() {
if entry.Status.Set(0) == 1 {
entry.cron.Stop()
// 创建定时任务
func newEntry(pattern string, job func(), name ... string) (*Entry, error) {
schedule, err := newSchedule(pattern)
if err != nil {
return nil, err
}
entry := &Entry {
mode : gtype.NewInt(),
status : gtype.NewInt(),
schedule : schedule,
Job : job,
JobName : runtime.FuncForPC(reflect.ValueOf(job).Pointer()).Name(),
Time : time.Now(),
}
if len(name) > 0 {
entry.Name = name[0]
}
return entry, nil
}
// 设置任务运行模式(0: normal; 1: singleton; 2: once)
func (entry *Entry) SetMode(mode int) {
entry.mode.Set(mode)
}
// 定时任务状态
func (entry *Entry) Status() int {
return entry.status.Val()
}
// 启动定时任务
func (entry *Entry) Start() {
entry.status.Set(STATUS_READY)
}
// 停止定时任务
func (entry *Entry) Stop() {
entry.status.Set(STATUS_CLOSED)
}

View File

@ -0,0 +1,65 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
package gcron
import (
"gitee.com/johng/gf/g/container/garray"
"time"
)
// 延迟添加定时任务delay参数单位为秒
func (c *Cron) startLoop() {
go func() {
for c.status.Val() != STATUS_CLOSED {
time.Sleep(time.Second)
if c.status.Val() == STATUS_RUNNING {
go c.checkEntries(time.Now())
}
}
}()
}
// 遍历检查可执行定时任务,并异步执行
func (c *Cron) checkEntries(t time.Time) {
removeArray := garray.NewStringArray(0, 0, false)
c.entries.RLockFunc(func(m map[string]interface{}) {
for _, v := range m {
entry := v.(*Entry)
if entry.schedule.meet(t) {
// 是否已命令停止运行
if entry.status.Val() == STATUS_CLOSED {
continue
}
switch entry.mode.Val() {
// 是否只允许单例运行
case MODE_SINGLETON:
if entry.status.Set(STATUS_RUNNING) == STATUS_RUNNING {
continue
}
// 只运行一次的任务
case MODE_ONCE:
if entry.status.Set(STATUS_CLOSED) == STATUS_CLOSED {
continue
}
removeArray.Append(entry.Name)
}
// 执行异步运行
go func() {
defer func() {
if entry.status.Val() != STATUS_CLOSED {
entry.status.Set(STATUS_READY)
}
}()
entry.Job()
}()
}
}
})
if removeArray.Len() > 0 {
c.entries.BatchRemove(removeArray.Slice())
}
}

View File

@ -0,0 +1,245 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
package gcron
import (
"errors"
"fmt"
"gitee.com/johng/gf/g/util/gregex"
"strconv"
"strings"
"time"
)
// 运行时间管理对象
type cronSchedule struct {
create int64 // 创建时间戳(秒)
every int64 // 运行时间间隔(秒)
pattern string // 原始注册字符串
second map[int]struct{}
minute map[int]struct{}
hour map[int]struct{}
day map[int]struct{}
week map[int]struct{}
month map[int]struct{}
}
const (
gREGEX_FOR_CRON = `^([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)$`
)
var (
// 预定义的定时格式
predefinedPatternMap = map[string]string{
"@yearly" : "0 0 0 1 1 *",
"@annually" : "0 0 0 1 1 *",
"@monthly" : "0 0 0 1 * *",
"@weekly" : "0 0 0 * * 0",
"@daily" : "0 0 0 * * *",
"@midnight" : "0 0 0 * * *",
"@hourly" : "0 0 * * * *",
}
// 月份与数字对应表
monthMap = map[string]int {
"jan": 1,
"feb": 2,
"mar": 3,
"apr": 4,
"may": 5,
"jun": 6,
"jul": 7,
"aug": 8,
"sep": 9,
"oct": 10,
"nov": 11,
"dec": 12,
}
// 星期与数字对应表
weekMap = map[string]int {
"sun": 0,
"mon": 1,
"tue": 2,
"wed": 3,
"thu": 4,
"fri": 5,
"sat": 6,
}
)
// 解析定时格式为cronSchedule对象
func newSchedule(pattern string) (*cronSchedule, error) {
// 处理预定义的定时格式
if match, _ := gregex.MatchString(`(@\w+)\s*(\w*)\s*`, pattern); len(match) > 0 {
key := strings.ToLower(match[1])
if v, ok := predefinedPatternMap[key]; ok {
pattern = v
} else if strings.Compare(key, "@every") == 0 {
if d, err := time.ParseDuration(match[2]); err != nil {
return nil, err
} else {
return &cronSchedule {
create : time.Now().Unix(),
every : int64(d.Seconds()),
pattern : pattern,
}, nil
}
} else {
return nil, errors.New(fmt.Sprintf(`invalid pattern: "%s"`, pattern))
}
}
// 处理通用的定时格式定义
if match, _ := gregex.MatchString(gREGEX_FOR_CRON, pattern); len(match) == 7 {
schedule := &cronSchedule {
create : time.Now().Unix(),
every : 0,
pattern : pattern,
}
// 秒
if m, err := parseItem(match[1], 0, 59, false); err != nil {
return nil, err
} else {
schedule.second = m
}
// 分
if m, err := parseItem(match[2], 0, 59, false); err != nil {
return nil, err
} else {
schedule.minute = m
}
// 时
if m, err := parseItem(match[3], 0, 23, false); err != nil {
return nil, err
} else {
schedule.hour = m
}
// 天
if m, err := parseItem(match[4], 0, 30, false); err != nil {
return nil, err
} else {
schedule.day = m
}
// 周
if m, err := parseItem(match[5], 0, 6, false); err != nil {
return nil, err
} else {
schedule.week = m
}
// 月
if m, err := parseItem(match[6], 1, 12, false); err != nil {
return nil, err
} else {
schedule.month = m
}
return schedule, nil
} else {
return nil, errors.New(fmt.Sprintf(`invalid pattern: "%s"`, pattern))
}
}
// 解析定时格式中的每一项定时配置
func parseItem(item string, min int, max int, allowQuestionMark bool) (map[int]struct{}, error) {
m := make(map[int]struct{}, max - min + 1)
if item == "*" || (allowQuestionMark && item == "?") {
for i := min; i <= max; i++ {
m[i] = struct{}{}
}
} else {
for _, item := range strings.Split(item, ",") {
interval := 1
intervalArray := strings.Split(item, "/")
if len(intervalArray) == 2 {
if i, err := strconv.Atoi(intervalArray[1]); err != nil {
return nil, errors.New(fmt.Sprintf(`invalid pattern item: "%s"`, item))
} else {
interval = i
}
}
rangeMin := min
rangeMax := max
rangeArray := strings.Split(intervalArray[0], "-")
valueType := byte(0)
switch max {
case 6: valueType = 'w'
case 11: valueType = 'm'
}
// 例如: */5
if rangeArray[0] != "*" {
if i, err := parseItemValue(rangeArray[0], valueType); err != nil {
return nil, errors.New(fmt.Sprintf(`invalid pattern item: "%s"`, item))
} else {
rangeMin = i
rangeMax = i
}
}
if len(rangeArray) == 2 {
if i, err := parseItemValue(rangeArray[1], valueType); err != nil {
return nil, errors.New(fmt.Sprintf(`invalid pattern item: "%s"`, item))
} else {
rangeMax = i
}
}
for i := rangeMin; i <= rangeMax; i += interval {
m[i] = struct{}{}
}
}
}
return m, nil
}
// 将配置项值转换为数字
func parseItemValue(value string, valueType byte) (int, error) {
if gregex.IsMatchString(`^\d+$`, value) {
// 纯数字
if i, err := strconv.Atoi(value); err == nil {
return i, nil
}
} else {
// 英文字母
switch valueType {
case 'w':
if i, ok := weekMap[strings.ToLower(value)]; ok {
return int(i), nil
}
case 'm':
if i, ok := monthMap[strings.ToLower(value)]; ok {
return int(i), nil
}
}
}
return 0, errors.New(fmt.Sprintf(`invalid pattern value: "%s"`, value))
}
// 判断给定的时间是否满足schedule
func (s *cronSchedule) meet(t time.Time) bool {
if s.every != 0 {
diff := t.Unix() - s.create
if diff > 0 {
return diff%s.every == 0
}
return false
} else {
if _, ok := s.second[t.Second()]; !ok {
return false
}
if _, ok := s.minute[t.Minute()]; !ok {
return false
}
if _, ok := s.hour[t.Hour()]; !ok {
return false
}
if _, ok := s.day[t.Day()]; !ok {
return false
}
if _, ok := s.week[int(t.Weekday())]; !ok {
return false
}
if _, ok := s.month[int(t.Month())]; !ok {
return false
}
return true
}
}

View File

@ -0,0 +1,74 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
package gcron_test
import (
"gitee.com/johng/gf/g/container/garray"
"gitee.com/johng/gf/g/os/gcron"
"gitee.com/johng/gf/g/util/gtest"
"testing"
"time"
)
var (
cron1 = gcron.New()
cron2 = gcron.New()
)
func TestCron_Add_Close(t *testing.T) {
array := garray.New(0, 0)
_, err1 := cron1.Add("* * * * * *", func() {
array.Append(1)
})
_, err2 := cron1.Add("* * * * * *", func() {
array.Append(1)
}, "test")
_, err3 := cron1.Add("* * * * * *", func() {
array.Append(1)
}, "test")
_, err4 := cron1.Add("@every 2s", func() {
array.Append(1)
})
gtest.Assert(err1, nil)
gtest.Assert(err2, nil)
gtest.AssertNE(err3, nil)
gtest.Assert(err4, nil)
time.Sleep(1100*time.Millisecond)
gtest.Assert(array.Len(), 2)
time.Sleep(1100*time.Millisecond)
gtest.Assert(array.Len(), 5)
cron1.Close()
time.Sleep(1100*time.Millisecond)
fixedLength := array.Len()
time.Sleep(1100*time.Millisecond)
gtest.Assert(array.Len(), fixedLength)
}
func TestCron_Entries(t *testing.T) {
entries := cron1.Entries()
gtest.Assert(len(entries), 3)
}
func TestCron_DelayAdd(t *testing.T) {
cron2.Add("* * * * * *", func() {}, "add")
cron2.DelayAdd(1, "* * * * * *", func() {}, "delay_add")
gtest.Assert(len(cron2.Entries()), 1)
time.Sleep(1100*time.Millisecond)
gtest.Assert(len(cron2.Entries()), 2)
}
func TestCron_Remove(t *testing.T) {
cron2.Remove("delay_add")
gtest.Assert(len(cron2.Entries()), 1)
}
func TestCron_Search(t *testing.T) {
entry1 := cron2.Search("add")
entry2 := cron2.Search("test-none")
gtest.AssertNE(entry1, nil)
gtest.Assert(entry2, nil)
}

View File

@ -0,0 +1,31 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
package gcron_test
import (
"gitee.com/johng/gf/g/container/garray"
"gitee.com/johng/gf/g/os/gcron"
"gitee.com/johng/gf/g/util/gtest"
"testing"
"time"
)
var (
singletonCron = gcron.New()
)
func TestCron_AddSingleton(t *testing.T) {
array := garray.New(0, 0)
singletonCron.AddSingleton("* * * * * *", func() {
array.Append(1)
time.Sleep(5*time.Second)
})
gtest.Assert(len(singletonCron.Entries()), 1)
time.Sleep(3500*time.Millisecond)
gtest.Assert(array.Len(), 1)
}

View File

@ -0,0 +1,33 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
package gcron_test
import (
"gitee.com/johng/gf/g/container/garray"
"gitee.com/johng/gf/g/os/gcron"
"gitee.com/johng/gf/g/util/gtest"
"testing"
"time"
)
var (
onceCron = gcron.New()
)
func TestCron_AddOnce(t *testing.T) {
array := garray.New(0, 0)
singletonCron.AddOnce("* * * * * *", func() {
array.Append(1)
})
singletonCron.AddOnce("* * * * * *", func() {
array.Append(1)
})
gtest.Assert(len(singletonCron.Entries()), 2)
time.Sleep(2500*time.Millisecond)
gtest.Assert(array.Len(), 2)
gtest.Assert(len(singletonCron.Entries()), 0)
}

View File

@ -24,12 +24,12 @@ func Unlock(key string) {
locker.Unlock(key)
}
// 内存读锁如果锁成功返回true失败则返回false;过期时间单位为默认为0表示不过期
// 内存读锁如果锁成功返回true失败则返回false; 过期时间单位为秒默认为0表示不过期
func TryRLock(key string, expire...int) bool {
return locker.TryRLock(key, expire...)
}
// 内存写锁锁成功返回true失败时阻塞当失败时表示有写锁存在;过期时间单位为默认为0表示不过期
// 内存写锁锁成功返回true失败时阻塞当失败时表示有写锁存在; 过期时间单位为秒默认为0表示不过期
func RLock(key string, expire...int) {
locker.RLock(key, expire...)
}

View File

@ -7,7 +7,9 @@
package gmlock
import (
"fmt"
"gitee.com/johng/gf/g/container/gmap"
"gitee.com/johng/gf/g/os/gcron"
"gitee.com/johng/gf/g/os/gtime"
"time"
)
@ -24,12 +26,12 @@ func New() *Locker {
}
}
// 内存写锁如果锁成功返回true失败则返回false;过期时间单位为默认为0表示不过期
// 内存写锁如果锁成功返回true失败则返回false; 过期时间单位为秒默认为0表示不过期
func (l *Locker) TryLock(key string, expire...int) bool {
return l.doLock(key, l.getExpire(expire...), true)
}
// 内存写锁锁成功返回true失败时阻塞当失败时表示有其他写锁存在;过期时间单位为默认为0表示不过期
// 内存写锁锁成功返回true失败时阻塞当失败时表示有其他写锁存在;过期时间单位为秒默认为0表示不过期
func (l *Locker) Lock(key string, expire...int) {
l.doLock(key, l.getExpire(expire...), false)
}
@ -41,12 +43,12 @@ func (l *Locker) Unlock(key string) {
}
}
// 内存读锁如果锁成功返回true失败则返回false;过期时间单位为默认为0表示不过期
// 内存读锁如果锁成功返回true失败则返回false; 过期时间单位为秒默认为0表示不过期
func (l *Locker) TryRLock(key string, expire...int) bool {
return l.doRLock(key, l.getExpire(expire...), true)
}
// 内存写锁锁成功返回true失败时阻塞当失败时表示有写锁存在;过期时间单位为默认为0表示不过期
// 内存写锁锁成功返回true失败时阻塞当失败时表示有写锁存在; 过期时间单位为秒默认为0表示不过期
func (l *Locker) RLock(key string, expire...int) {
l.doRLock(key, l.getExpire(expire...), false)
}
@ -98,9 +100,8 @@ func (l *Locker) doRLock(key string, expire int, try bool) bool {
mu.RLock()
}
if ok && expire > 0 {
// 异步goroutine计时处理
rid := mu.rid.Val()
gtime.SetTimeout(time.Duration(expire)*time.Millisecond, func() {
gcron.AddOnce(fmt.Sprintf(`@every %ds`, expire), func() {
if rid == mu.rid.Val() {
mu.RUnlock()
}

View File

@ -5,14 +5,14 @@
// You can obtain one at https://gitee.com/johng/gf.
// Goroutine池.
// 用于goroutine复用提升异步操作执行效率.
// 用于goroutine复用提升异步操作执行效率(避免goroutine限制并节约内存开销).
// 需要注意的是grpool提供给的公共池不提供关闭方法自创建的池可以手动关闭掉。
package grpool
import (
"math"
"gitee.com/johng/gf/g/container/glist"
"gitee.com/johng/gf/g/container/gtype"
"math"
)
// goroutine池对象

View File

@ -4,7 +4,7 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// 时间管理
// Package gtime provides functionality for measuring and displaying time/时间管理.
package gtime
import (
@ -120,7 +120,7 @@ func Millisecond() int64 {
// 获取当前的秒数(时间戳)
func Second() int64 {
return time.Now().UnixNano()/1e9
return time.Now().Unix()
}
// 获得当前的日期(例如2006-01-02)

60
g/os/gtimec/gtimec.go Normal file
View File

@ -0,0 +1,60 @@
// Copyright 2019 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// Package gtimec provides Time Circle for interval job running management/时间轮.
// 高效的时间轮任务执行管理,用于管理异步的间隔运行任务,或者异步延迟只运行一次的任务(最小时间粒度为秒)。
// 与其他定时任务管理模块的区别是,时间轮模块只管理间隔执行任务,并且更注重执行效率(纳秒级别)。
package gtimec
const (
MODE_NORMAL = 0
MODE_SINGLETON = 1
MODE_ONCE = 2
STATUS_READY = 0
STATUS_RUNNING = 1
STATUS_CLOSED = -1
)
var (
// 默认的circle管理对象
defaultCircle = New()
)
// 添加执行方法,可以给定名字,以便于后续执行删除
func Add(interval int, job func()) *Entry {
return defaultCircle.Add(interval, job)
}
// 添加单例运行循环任务
func AddSingleton(interval int, job func()) *Entry {
return defaultCircle.AddSingleton(interval, job)
}
// 添加只运行一次的循环任务
func AddOnce(interval int, job func()) *Entry {
return defaultCircle.AddOnce(interval, job)
}
// 延迟添加循环任务delay参数单位为秒
func DelayAdd(delay int, interval int, job func()) {
defaultCircle.DelayAdd(delay, interval, job)
}
// 延迟添加单例循环任务delay参数单位为秒
func DelayAddSingleton(delay int, interval int, job func()) {
defaultCircle.DelayAddSingleton(delay, interval, job)
}
// 延迟添加只运行一次的循环任务delay参数单位为秒
func DelayAddOnce(delay int, interval int, job func()) {
defaultCircle.DelayAddOnce(delay, interval, job)
}
//// 获取所有已注册的循环任务项
//func Entries() []*Entry {
// return defaultCircle.Entries()
//}

View File

@ -0,0 +1,20 @@
// Copyright 2019 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
package gtimec_test
import (
"gitee.com/johng/gf/g/os/gtimec"
"testing"
)
func Benchmark_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
gtimec.Add(1, func() {
})
}
}

View File

@ -0,0 +1,103 @@
// Copyright 2019 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
package gtimec
import (
"gitee.com/johng/gf/g/container/glist"
"gitee.com/johng/gf/g/container/gtype"
"time"
)
// 循环任务管理对象
type Circle struct {
status *gtype.Int // 循环任务状态(0: 未执行; 1: 运行中; -1:删除关闭)
entries *glist.List // 所有的循环任务项
}
// 创建自定义的循环任务管理对象
func New() *Circle {
circle := &Circle {
status : gtype.NewInt(STATUS_RUNNING),
entries : glist.New(),
}
circle.startLoop()
return circle
}
// 添加循环任务
func (c *Circle) Add(interval int, job func()) *Entry {
entry := newEntry(interval, job, MODE_NORMAL)
c.entries.PushBack(entry)
return entry
}
// 添加单例运行循环任务
func (c *Circle) AddSingleton(interval int, job func()) *Entry {
entry := newEntry(interval, job, MODE_SINGLETON)
c.entries.PushBack(entry)
return entry
}
// 添加只运行一次的循环任务
func (c *Circle) AddOnce(interval int, job func()) *Entry {
entry := newEntry(interval, job, MODE_ONCE)
c.entries.PushBack(entry)
return entry
}
// 延迟添加循环任务delay参数单位为秒
func (c *Circle) DelayAdd(delay int, interval int, job func()) {
go func() {
time.Sleep(time.Duration(delay)*time.Second)
c.Add(interval, job)
}()
}
// 延迟添加单例循环任务delay参数单位为秒
func (c *Circle) DelayAddSingleton(delay int, interval int, job func()) {
go func() {
time.Sleep(time.Duration(delay)*time.Second)
c.AddSingleton(interval, job)
}()
}
// 延迟添加只运行一次的循环任务delay参数单位为秒
func (c *Circle) DelayAddOnce(delay int, interval int, job func()) {
go func() {
time.Sleep(time.Duration(delay)*time.Second)
c.AddOnce(interval, job)
}()
}
// 关闭循环任务
func (c *Circle) Close() {
c.status.Set(STATUS_CLOSED)
}
//// 获取所有已注册的循环任务项(按照注册时间从小到大进行排序)
//func (c *Circle) Entries() []*Entry {
// array := garray.NewSortedArray(c.entries.Len(), func(v1, v2 interface{}) int {
// entry1 := v1.(*Entry)
// entry2 := v2.(*Entry)
// if entry1.Create > entry2.Create {
// return 1
// }
// return -1
// }, false)
// c.entries.RLockFunc(func(m map[string]interface{}) {
// for _, v := range m {
// array.Add(v.(*Entry))
// }
// })
// entries := make([]*Entry, array.Len())
// array.RLockFunc(func(array []interface{}) {
// for k, v := range array {
// entries[k] = v.(*Entry)
// }
// })
// return entries
//}

View File

@ -0,0 +1,62 @@
// Copyright 2019 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
package gtimec
import (
"gitee.com/johng/gf/g/container/gtype"
"time"
)
// 循环任务项
type Entry struct {
mode *gtype.Int // 任务运行模式(0: normal; 1: singleton; 2: once)
status *gtype.Int // 循环任务状态(0: ready; 1: running; -1: stopped)
Job func() // 注册循环任务方法
Create int64 // 创建时间戳(秒)
Interval int64 // 运行间隔(秒)
}
// 创建循环任务
func newEntry(interval int, job func(), mode int) *Entry {
return &Entry {
mode : gtype.NewInt(mode),
status : gtype.NewInt(),
Job : job,
Create : time.Now().Unix(),
Interval : int64(interval),
}
}
// 设置任务运行模式(0: normal; 1: singleton; 2: once)
func (entry *Entry) SetMode(mode int) {
entry.mode.Set(mode)
}
// 循环任务状态
func (entry *Entry) Status() int {
return entry.status.Val()
}
// 启动循环任务
func (entry *Entry) Start() {
entry.status.Set(STATUS_READY)
}
// 停止循环任务
func (entry *Entry) Stop() {
entry.status.Set(STATUS_CLOSED)
}
// 给定时间是否满足当前循环任务运行间隔
func (entry *Entry) meet(t time.Time) bool {
diff := t.Unix() - entry.Create
if diff > 0 {
return diff%entry.Interval == 0
}
return false
}

View File

@ -0,0 +1,72 @@
// Copyright 2019 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
package gtimec
import (
"container/list"
"gitee.com/johng/gf/g/container/garray"
"gitee.com/johng/gf/g/os/glog"
"time"
)
// 延迟添加循环任务delay参数单位为秒
func (c *Circle) startLoop() {
go func() {
for c.status.Val() != STATUS_CLOSED {
time.Sleep(time.Second)
glog.Println("hello")
if c.status.Val() == STATUS_RUNNING {
go c.checkEntries(time.Now())
}
}
}()
}
// 遍历检查可执行循环任务,并异步执行
func (c *Circle) checkEntries(t time.Time) {
removeArray := garray.NewArray(0, 0, false)
c.entries.RLockFunc(func(l *list.List) {
for e := l.Front(); e != nil; e = e.Next() {
entry := e.Value.(*Entry)
if entry.meet(t) {
// 是否已命令停止运行
if entry.status.Val() == STATUS_CLOSED {
continue
}
switch entry.mode.Val() {
// 是否只允许单例运行
case MODE_SINGLETON:
if entry.status.Set(STATUS_RUNNING) == STATUS_RUNNING {
continue
}
// 只运行一次的任务
case MODE_ONCE:
if entry.status.Set(STATUS_CLOSED) == STATUS_CLOSED {
continue
}
removeArray.Append(e)
}
// 执行异步运行
//go func() {
// //defer func() {
// // if entry.status.Val() != STATUS_CLOSED {
// // entry.status.Set(STATUS_READY)
// // }
// //}()
// //entry.Job()
//}()
}
}
})
if removeArray.Len() > 0 {
c.entries.LockFunc(func(l *list.List) {
for _, v := range removeArray.Slice() {
l.Remove(v.(*list.Element))
}
})
}
}

View File

@ -15,6 +15,11 @@ import (
"strings"
)
// 转换为string类型的接口
type apiString interface {
String() string
}
// 将变量i转换为字符串指定的类型t非必须参数extraParams用以额外的参数传递
func Convert(i interface{}, t string, extraParams...interface{}) interface{} {
switch t {
@ -80,9 +85,14 @@ func String(i interface{}) string {
case string: return value
case []byte: return string(value)
default:
// 默认使用json进行字符串转换
jsonContent, _ := json.Marshal(value)
return string(jsonContent)
if f, ok := value.(apiString); ok {
// 如果变量实现了String()接口,那么使用该接口执行转换
return f.String()
} else {
// 默认使用json进行字符串转换
jsonContent, _ := json.Marshal(value)
return string(jsonContent)
}
}
}

View File

@ -4,8 +4,7 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// Package gtest provides useful test utils.
// 测试模块.
// Package gtest provides useful test utils/测试模块.
package gtest
import (
@ -13,20 +12,194 @@ import (
"gitee.com/johng/gf/g/os/glog"
"gitee.com/johng/gf/g/util/gconv"
"os"
"reflect"
)
// 断言判断
// 断言判断, 相等
func Assert(value, expect interface{}) {
if gconv.String(value) != gconv.String(expect) {
glog.Printfln(`[ASSERT] VALUE: %v, EXPECT: %v`, value, expect)
rv := reflect.ValueOf(value)
if rv.Kind() == reflect.Ptr {
if rv.IsNil() {
value = nil
}
}
if value != expect {
glog.To(os.Stderr).Printfln(`[ASSERT] EXPECT %v == %v`, value, expect)
glog.Header(false).PrintBacktrace(1)
os.Exit(1)
}
}
// 断言判断, 相等
func AssertEQ(value, expect interface{}) {
rv := reflect.ValueOf(value)
if rv.Kind() == reflect.Ptr {
if rv.IsNil() {
value = nil
}
}
if value != expect {
glog.To(os.Stderr).Printfln(`[ASSERT] EXPECT %v == %v`, value, expect)
glog.Header(false).PrintBacktrace(1)
os.Exit(1)
}
}
// 断言判断, 不相等
func AssertNE(value, expect interface{}) {
rv := reflect.ValueOf(value)
if rv.Kind() == reflect.Ptr {
if rv.IsNil() {
value = nil
}
}
if value == expect {
glog.To(os.Stderr).Printfln(`[ASSERT] EXPECT %v != %v`, value, expect)
glog.Header(false).PrintBacktrace(1)
os.Exit(1)
}
}
// 断言判断, value > expect; 注意: 仅有字符串、整形、浮点型才可以比较
func AssertGT(value, expect interface{}) {
passed := false
switch reflect.ValueOf(expect).Kind() {
case reflect.String:
passed = gconv.String(value) > gconv.String(expect)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
passed = gconv.Int(value) > gconv.Int(expect)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
passed = gconv.Uint(value) > gconv.Uint(expect)
case reflect.Float32, reflect.Float64:
passed = gconv.Float64(value) > gconv.Float64(expect)
}
if !passed {
glog.To(os.Stderr).Printfln(`[ASSERT] EXPECT %v > %v`, value, expect)
glog.Header(false).PrintBacktrace(1)
os.Exit(1)
}
}
// 断言判断, value >= expect; 注意: 仅有字符串、整形、浮点型才可以比较
func AssertGTE(value, expect interface{}) {
passed := false
switch reflect.ValueOf(expect).Kind() {
case reflect.String:
passed = gconv.String(value) >= gconv.String(expect)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
passed = gconv.Int(value) >= gconv.Int(expect)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
passed = gconv.Uint(value) >= gconv.Uint(expect)
case reflect.Float32, reflect.Float64:
passed = gconv.Float64(value) >= gconv.Float64(expect)
}
if !passed {
glog.To(os.Stderr).Printfln(`[ASSERT] EXPECT %v >= %v`, value, expect)
glog.Header(false).PrintBacktrace(1)
os.Exit(1)
}
}
// 断言判断, value < expect; 注意: 仅有字符串、整形、浮点型才可以比较
func AssertLT(value, expect interface{}) {
passed := false
switch reflect.ValueOf(expect).Kind() {
case reflect.String:
passed = gconv.String(value) < gconv.String(expect)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
passed = gconv.Int(value) < gconv.Int(expect)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
passed = gconv.Uint(value) < gconv.Uint(expect)
case reflect.Float32, reflect.Float64:
passed = gconv.Float64(value) < gconv.Float64(expect)
}
if !passed {
glog.To(os.Stderr).Printfln(`[ASSERT] EXPECT %v < %v`, value, expect)
glog.Header(false).PrintBacktrace(1)
os.Exit(1)
}
}
// 断言判断, value <= expect; 注意: 仅有字符串、整形、浮点型才可以比较
func AssertLTE(value, expect interface{}) {
passed := false
switch reflect.ValueOf(expect).Kind() {
case reflect.String:
passed = gconv.String(value) <= gconv.String(expect)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
passed = gconv.Int(value) <= gconv.Int(expect)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
passed = gconv.Uint(value) <= gconv.Uint(expect)
case reflect.Float32, reflect.Float64:
passed = gconv.Float64(value) <= gconv.Float64(expect)
}
if !passed {
glog.To(os.Stderr).Printfln(`[ASSERT] EXPECT %v <= %v`, value, expect)
glog.Header(false).PrintBacktrace(1)
os.Exit(1)
}
}
// 断言判断, value IN expect; 注意: expect必须为slice类型
func AssertIN(value, expect interface{}) {
passed := false
switch reflect.ValueOf(expect).Kind() {
case reflect.Slice, reflect.Array:
for _, v := range gconv.Interfaces(expect) {
if v == value {
passed = true
break
}
}
}
if !passed {
glog.To(os.Stderr).Printfln(`[ASSERT] EXPECT %v IN %v`, value, expect)
glog.Header(false).PrintBacktrace(1)
os.Exit(1)
}
}
// 断言判断, value NOT IN expect; 注意: expect必须为slice类型
func AssertNI(value, expect interface{}) {
passed := false
switch reflect.ValueOf(expect).Kind() {
case reflect.Slice, reflect.Array:
for _, v := range gconv.Interfaces(expect) {
if v == value {
passed = true
break
}
}
}
if passed {
glog.To(os.Stderr).Printfln(`[ASSERT] EXPECT %v NOT IN %v`, value, expect)
glog.Header(false).PrintBacktrace(1)
os.Exit(1)
}
}
// 提示错误不退出
func Error(message...interface{}) {
glog.To(os.Stderr).Println(`[ERROR]`, fmt.Sprint(message...))
glog.Header(false).PrintBacktrace(1)
}
// 提示错误并退出
func Fatal(message...interface{}) {
glog.Println(`[FATAL]`, fmt.Sprint(message...))
glog.To(os.Stderr).Println(`[FATAL]`, fmt.Sprint(message...))
glog.Header(false).PrintBacktrace(1)
os.Exit(1)
}

View File

@ -1,18 +1,20 @@
package main
import (
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/os/gcron"
"gitee.com/johng/gf/g/os/glog"
"time"
)
func main() {
gcron.Add("0 30 * * * *", func() { glog.Println("Every hour on the half hour") })
gcron.Add("* * * * * *", func() { glog.Println("Every second") }, "second-cron")
gcron.Add("@hourly", func() { glog.Println("Every hour") })
gcron.Add("@every 1h30m", func() { glog.Println("Every hour thirty") })
g.Dump(gcron.Entries())
gcron.Add("0 30 * * * *", func() { glog.Println("Every hour on the half hour") })
gcron.Add("* * * * * *", func() { glog.Println("Every second, pattern") }, "second-cron")
gcron.Add("*/5 * * * * *", func() { glog.Println("Every 5 seconds, pattern") })
gcron.Add("@hourly", func() { glog.Println("Every hour") })
gcron.Add("@every 1h30m", func() { glog.Println("Every hour thirty") })
gcron.Add("@every 1s", func() { glog.Println("Every 1 second") })
gcron.Add("@every 5s", func() { glog.Println("Every 5 seconds") })
time.Sleep(3*time.Second)
@ -22,5 +24,5 @@ func main() {
gcron.Start("second-cron")
time.Sleep(3*time.Second)
time.Sleep(10*time.Second)
}

View File

@ -1,33 +1,8 @@
package main
import (
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/frame/gmvc"
import "fmt"
)
type User struct {
gmvc.Controller
}
func main(){
s := g.Server()
s.BindController("/user", new(User))
s.BindController("/user/{.method}/{uid}", new(User), "Info")
s.BindController("/user/{.method}/{page}.html", new(User), "List")
s.SetPort(8293)
s.Run()
}
func (u *User) Index() {
u.Response.Write("User")
}
func (u *User) Info() {
u.Response.Write("Info - Uid: ", u.Request.Get("uid"))
}
func (u *User) List() {
u.Response.Write("List - Page: ", u.Request.Get("page"))
fmt.Println(fmt.Sprintf(`@every %ds`, 12345))
}

View File

@ -1,22 +0,0 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe

View File

@ -1 +0,0 @@
language: go

View File

@ -1,21 +0,0 @@
Copyright (C) 2012 Rob Figueiredo
All Rights Reserved.
MIT LICENSE
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,6 +0,0 @@
[![GoDoc](http://godoc.org/github.com/robfig/cron?status.png)](http://godoc.org/github.com/robfig/cron)
[![Build Status](https://travis-ci.org/robfig/cron.svg?branch=master)](https://travis-ci.org/robfig/cron)
# cron
Documentation here: https://godoc.org/github.com/robfig/cron

View File

@ -1,27 +0,0 @@
package cron
import "time"
// ConstantDelaySchedule represents a simple recurring duty cycle, e.g. "Every 5 minutes".
// It does not support jobs more frequent than once a second.
type ConstantDelaySchedule struct {
Delay time.Duration
}
// Every returns a crontab Schedule that activates once every duration.
// Delays of less than a second are not supported (will round up to 1 second).
// Any fields less than a Second are truncated.
func Every(duration time.Duration) ConstantDelaySchedule {
if duration < time.Second {
duration = time.Second
}
return ConstantDelaySchedule{
Delay: duration - time.Duration(duration.Nanoseconds())%time.Second,
}
}
// Next returns the next time this should be run.
// This rounds so that the next activation time will be on the second.
func (schedule ConstantDelaySchedule) Next(t time.Time) time.Time {
return t.Add(schedule.Delay - time.Duration(t.Nanosecond())*time.Nanosecond)
}

View File

@ -1,54 +0,0 @@
package cron
import (
"testing"
"time"
)
func TestConstantDelayNext(t *testing.T) {
tests := []struct {
time string
delay time.Duration
expected string
}{
// Simple cases
{"Mon Jul 9 14:45 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"},
{"Mon Jul 9 14:59 2012", 15 * time.Minute, "Mon Jul 9 15:14 2012"},
{"Mon Jul 9 14:59:59 2012", 15 * time.Minute, "Mon Jul 9 15:14:59 2012"},
// Wrap around hours
{"Mon Jul 9 15:45 2012", 35 * time.Minute, "Mon Jul 9 16:20 2012"},
// Wrap around days
{"Mon Jul 9 23:46 2012", 14 * time.Minute, "Tue Jul 10 00:00 2012"},
{"Mon Jul 9 23:45 2012", 35 * time.Minute, "Tue Jul 10 00:20 2012"},
{"Mon Jul 9 23:35:51 2012", 44*time.Minute + 24*time.Second, "Tue Jul 10 00:20:15 2012"},
{"Mon Jul 9 23:35:51 2012", 25*time.Hour + 44*time.Minute + 24*time.Second, "Thu Jul 11 01:20:15 2012"},
// Wrap around months
{"Mon Jul 9 23:35 2012", 91*24*time.Hour + 25*time.Minute, "Thu Oct 9 00:00 2012"},
// Wrap around minute, hour, day, month, and year
{"Mon Dec 31 23:59:45 2012", 15 * time.Second, "Tue Jan 1 00:00:00 2013"},
// Round to nearest second on the delay
{"Mon Jul 9 14:45 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"},
// Round up to 1 second if the duration is less.
{"Mon Jul 9 14:45:00 2012", 15 * time.Millisecond, "Mon Jul 9 14:45:01 2012"},
// Round to nearest second when calculating the next time.
{"Mon Jul 9 14:45:00.005 2012", 15 * time.Minute, "Mon Jul 9 15:00 2012"},
// Round to nearest second for both.
{"Mon Jul 9 14:45:00.005 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"},
}
for _, c := range tests {
actual := Every(c.delay).Next(getTime(c.time))
expected := getTime(c.expected)
if actual != expected {
t.Errorf("%s, \"%s\": (expected) %v != %v (actual)", c.time, c.delay, expected, actual)
}
}
}

View File

@ -1,259 +0,0 @@
package cron
import (
"log"
"runtime"
"sort"
"time"
)
// Cron keeps track of any number of entries, invoking the associated func as
// specified by the schedule. It may be started, stopped, and the entries may
// be inspected while running.
type Cron struct {
entries []*Entry
stop chan struct{}
add chan *Entry
snapshot chan []*Entry
running bool
ErrorLog *log.Logger
location *time.Location
}
// Job is an interface for submitted cron jobs.
type Job interface {
Run()
}
// The Schedule describes a job's duty cycle.
type Schedule interface {
// Return the next activation time, later than the given time.
// Next is invoked initially, and then each time the job is run.
Next(time.Time) time.Time
}
// Entry consists of a schedule and the func to execute on that schedule.
type Entry struct {
// The schedule on which this job should be run.
Schedule Schedule
// The next time the job will run. This is the zero time if Cron has not been
// started or this entry's schedule is unsatisfiable
Next time.Time
// The last time this job was run. This is the zero time if the job has never
// been run.
Prev time.Time
// The Job to run.
Job Job
}
// byTime is a wrapper for sorting the entry array by time
// (with zero time at the end).
type byTime []*Entry
func (s byTime) Len() int { return len(s) }
func (s byTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s byTime) Less(i, j int) bool {
// Two zero times should return false.
// Otherwise, zero is "greater" than any other time.
// (To sort it at the end of the list.)
if s[i].Next.IsZero() {
return false
}
if s[j].Next.IsZero() {
return true
}
return s[i].Next.Before(s[j].Next)
}
// New returns a new Cron job runner, in the Local time zone.
func New() *Cron {
return NewWithLocation(time.Now().Location())
}
// NewWithLocation returns a new Cron job runner.
func NewWithLocation(location *time.Location) *Cron {
return &Cron{
entries: nil,
add: make(chan *Entry),
stop: make(chan struct{}),
snapshot: make(chan []*Entry),
running: false,
ErrorLog: nil,
location: location,
}
}
// A wrapper that turns a func() into a cron.Job
type FuncJob func()
func (f FuncJob) Run() { f() }
// AddFunc adds a func to the Cron to be run on the given schedule.
func (c *Cron) AddFunc(spec string, cmd func()) error {
return c.AddJob(spec, FuncJob(cmd))
}
// AddJob adds a Job to the Cron to be run on the given schedule.
func (c *Cron) AddJob(spec string, cmd Job) error {
schedule, err := Parse(spec)
if err != nil {
return err
}
c.Schedule(schedule, cmd)
return nil
}
// Schedule adds a Job to the Cron to be run on the given schedule.
func (c *Cron) Schedule(schedule Schedule, cmd Job) {
entry := &Entry{
Schedule: schedule,
Job: cmd,
}
if !c.running {
c.entries = append(c.entries, entry)
return
}
c.add <- entry
}
// Entries returns a snapshot of the cron entries.
func (c *Cron) Entries() []*Entry {
if c.running {
c.snapshot <- nil
x := <-c.snapshot
return x
}
return c.entrySnapshot()
}
// Location gets the time zone location
func (c *Cron) Location() *time.Location {
return c.location
}
// Start the cron scheduler in its own go-routine, or no-op if already started.
func (c *Cron) Start() {
if c.running {
return
}
c.running = true
go c.run()
}
// Run the cron scheduler, or no-op if already running.
func (c *Cron) Run() {
if c.running {
return
}
c.running = true
c.run()
}
func (c *Cron) runWithRecovery(j Job) {
defer func() {
if r := recover(); r != nil {
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
c.logf("cron: panic running job: %v\n%s", r, buf)
}
}()
j.Run()
}
// Run the scheduler. this is private just due to the need to synchronize
// access to the 'running' state variable.
func (c *Cron) run() {
// Figure out the next activation times for each entry.
now := c.now()
for _, entry := range c.entries {
entry.Next = entry.Schedule.Next(now)
}
for {
// Determine the next entry to run.
sort.Sort(byTime(c.entries))
var timer *time.Timer
if len(c.entries) == 0 || c.entries[0].Next.IsZero() {
// If there are no entries yet, just sleep - it still handles new entries
// and stop requests.
timer = time.NewTimer(100000 * time.Hour)
} else {
timer = time.NewTimer(c.entries[0].Next.Sub(now))
}
for {
select {
case now = <-timer.C:
now = now.In(c.location)
// Run every entry whose next time was less than now
for _, e := range c.entries {
if e.Next.After(now) || e.Next.IsZero() {
break
}
go c.runWithRecovery(e.Job)
e.Prev = e.Next
e.Next = e.Schedule.Next(now)
}
case newEntry := <-c.add:
timer.Stop()
now = c.now()
newEntry.Next = newEntry.Schedule.Next(now)
c.entries = append(c.entries, newEntry)
case <-c.snapshot:
c.snapshot <- c.entrySnapshot()
continue
case <-c.stop:
timer.Stop()
return
}
break
}
}
}
// Logs an error to stderr or to the configured error log
func (c *Cron) logf(format string, args ...interface{}) {
if c.ErrorLog != nil {
c.ErrorLog.Printf(format, args...)
} else {
log.Printf(format, args...)
}
}
// Stop stops the cron scheduler if it is running; otherwise it does nothing.
func (c *Cron) Stop() {
if !c.running {
return
}
c.stop <- struct{}{}
c.running = false
}
// entrySnapshot returns a copy of the current cron entry list.
func (c *Cron) entrySnapshot() []*Entry {
entries := []*Entry{}
for _, e := range c.entries {
entries = append(entries, &Entry{
Schedule: e.Schedule,
Next: e.Next,
Prev: e.Prev,
Job: e.Job,
})
}
return entries
}
// now returns current time in c location
func (c *Cron) now() time.Time {
return time.Now().In(c.location)
}

View File

@ -1,416 +0,0 @@
package cron
import (
"fmt"
"sync"
"testing"
"time"
)
// Many tests schedule a job for every second, and then wait at most a second
// for it to run. This amount is just slightly larger than 1 second to
// compensate for a few milliseconds of runtime.
const OneSecond = 1*time.Second + 10*time.Millisecond
func TestFuncPanicRecovery(t *testing.T) {
cron := New()
cron.Start()
defer cron.Stop()
cron.AddFunc("* * * * * ?", func() { panic("YOLO") })
select {
case <-time.After(OneSecond):
return
}
}
type DummyJob struct{}
func (d DummyJob) Run() {
panic("YOLO")
}
func TestJobPanicRecovery(t *testing.T) {
var job DummyJob
cron := New()
cron.Start()
defer cron.Stop()
cron.AddJob("* * * * * ?", job)
select {
case <-time.After(OneSecond):
return
}
}
// Start and stop cron with no entries.
func TestNoEntries(t *testing.T) {
cron := New()
cron.Start()
select {
case <-time.After(OneSecond):
t.Fatal("expected cron will be stopped immediately")
case <-stop(cron):
}
}
// Start, stop, then add an entry. Verify entry doesn't run.
func TestStopCausesJobsToNotRun(t *testing.T) {
wg := &sync.WaitGroup{}
wg.Add(1)
cron := New()
cron.Start()
cron.Stop()
cron.AddFunc("* * * * * ?", func() { wg.Done() })
select {
case <-time.After(OneSecond):
// No job ran!
case <-wait(wg):
t.Fatal("expected stopped cron does not run any job")
}
}
// Add a job, start cron, expect it runs.
func TestAddBeforeRunning(t *testing.T) {
wg := &sync.WaitGroup{}
wg.Add(1)
cron := New()
cron.AddFunc("* * * * * ?", func() { wg.Done() })
cron.Start()
defer cron.Stop()
// Give cron 2 seconds to run our job (which is always activated).
select {
case <-time.After(OneSecond):
t.Fatal("expected job runs")
case <-wait(wg):
}
}
// Start cron, add a job, expect it runs.
func TestAddWhileRunning(t *testing.T) {
wg := &sync.WaitGroup{}
wg.Add(1)
cron := New()
cron.Start()
defer cron.Stop()
cron.AddFunc("* * * * * ?", func() { wg.Done() })
select {
case <-time.After(OneSecond):
t.Fatal("expected job runs")
case <-wait(wg):
}
}
// Test for #34. Adding a job after calling start results in multiple job invocations
func TestAddWhileRunningWithDelay(t *testing.T) {
cron := New()
cron.Start()
defer cron.Stop()
time.Sleep(5 * time.Second)
var calls = 0
cron.AddFunc("* * * * * *", func() { calls += 1 })
<-time.After(OneSecond)
if calls != 1 {
t.Errorf("called %d times, expected 1\n", calls)
}
}
// Test timing with Entries.
func TestSnapshotEntries(t *testing.T) {
wg := &sync.WaitGroup{}
wg.Add(1)
cron := New()
cron.AddFunc("@every 2s", func() { wg.Done() })
cron.Start()
defer cron.Stop()
// Cron should fire in 2 seconds. After 1 second, call Entries.
select {
case <-time.After(OneSecond):
cron.Entries()
}
// Even though Entries was called, the cron should fire at the 2 second mark.
select {
case <-time.After(OneSecond):
t.Error("expected job runs at 2 second mark")
case <-wait(wg):
}
}
// Test that the entries are correctly sorted.
// Add a bunch of long-in-the-future entries, and an immediate entry, and ensure
// that the immediate entry runs immediately.
// Also: Test that multiple jobs run in the same instant.
func TestMultipleEntries(t *testing.T) {
wg := &sync.WaitGroup{}
wg.Add(2)
cron := New()
cron.AddFunc("0 0 0 1 1 ?", func() {})
cron.AddFunc("* * * * * ?", func() { wg.Done() })
cron.AddFunc("0 0 0 31 12 ?", func() {})
cron.AddFunc("* * * * * ?", func() { wg.Done() })
cron.Start()
defer cron.Stop()
select {
case <-time.After(OneSecond):
t.Error("expected job run in proper order")
case <-wait(wg):
}
}
// Test running the same job twice.
func TestRunningJobTwice(t *testing.T) {
wg := &sync.WaitGroup{}
wg.Add(2)
cron := New()
cron.AddFunc("0 0 0 1 1 ?", func() {})
cron.AddFunc("0 0 0 31 12 ?", func() {})
cron.AddFunc("* * * * * ?", func() { wg.Done() })
cron.Start()
defer cron.Stop()
select {
case <-time.After(2 * OneSecond):
t.Error("expected job fires 2 times")
case <-wait(wg):
}
}
func TestRunningMultipleSchedules(t *testing.T) {
wg := &sync.WaitGroup{}
wg.Add(2)
cron := New()
cron.AddFunc("0 0 0 1 1 ?", func() {})
cron.AddFunc("0 0 0 31 12 ?", func() {})
cron.AddFunc("* * * * * ?", func() { wg.Done() })
cron.Schedule(Every(time.Minute), FuncJob(func() {}))
cron.Schedule(Every(time.Second), FuncJob(func() { wg.Done() }))
cron.Schedule(Every(time.Hour), FuncJob(func() {}))
cron.Start()
defer cron.Stop()
select {
case <-time.After(2 * OneSecond):
t.Error("expected job fires 2 times")
case <-wait(wg):
}
}
// Test that the cron is run in the local time zone (as opposed to UTC).
func TestLocalTimezone(t *testing.T) {
wg := &sync.WaitGroup{}
wg.Add(2)
now := time.Now()
spec := fmt.Sprintf("%d,%d %d %d %d %d ?",
now.Second()+1, now.Second()+2, now.Minute(), now.Hour(), now.Day(), now.Month())
cron := New()
cron.AddFunc(spec, func() { wg.Done() })
cron.Start()
defer cron.Stop()
select {
case <-time.After(OneSecond * 2):
t.Error("expected job fires 2 times")
case <-wait(wg):
}
}
// Test that the cron is run in the given time zone (as opposed to local).
func TestNonLocalTimezone(t *testing.T) {
wg := &sync.WaitGroup{}
wg.Add(2)
loc, err := time.LoadLocation("Atlantic/Cape_Verde")
if err != nil {
fmt.Printf("Failed to load time zone Atlantic/Cape_Verde: %+v", err)
t.Fail()
}
now := time.Now().In(loc)
spec := fmt.Sprintf("%d,%d %d %d %d %d ?",
now.Second()+1, now.Second()+2, now.Minute(), now.Hour(), now.Day(), now.Month())
cron := NewWithLocation(loc)
cron.AddFunc(spec, func() { wg.Done() })
cron.Start()
defer cron.Stop()
select {
case <-time.After(OneSecond * 2):
t.Error("expected job fires 2 times")
case <-wait(wg):
}
}
// Test that calling stop before start silently returns without
// blocking the stop channel.
func TestStopWithoutStart(t *testing.T) {
cron := New()
cron.Stop()
}
type testJob struct {
wg *sync.WaitGroup
name string
}
func (t testJob) Run() {
t.wg.Done()
}
// Test that adding an invalid job spec returns an error
func TestInvalidJobSpec(t *testing.T) {
cron := New()
err := cron.AddJob("this will not parse", nil)
if err == nil {
t.Errorf("expected an error with invalid spec, got nil")
}
}
// Test blocking run method behaves as Start()
func TestBlockingRun(t *testing.T) {
wg := &sync.WaitGroup{}
wg.Add(1)
cron := New()
cron.AddFunc("* * * * * ?", func() { wg.Done() })
var unblockChan = make(chan struct{})
go func() {
cron.Run()
close(unblockChan)
}()
defer cron.Stop()
select {
case <-time.After(OneSecond):
t.Error("expected job fires")
case <-unblockChan:
t.Error("expected that Run() blocks")
case <-wait(wg):
}
}
// Test that double-running is a no-op
func TestStartNoop(t *testing.T) {
var tickChan = make(chan struct{}, 2)
cron := New()
cron.AddFunc("* * * * * ?", func() {
tickChan <- struct{}{}
})
cron.Start()
defer cron.Stop()
// Wait for the first firing to ensure the runner is going
<-tickChan
cron.Start()
<-tickChan
// Fail if this job fires again in a short period, indicating a double-run
select {
case <-time.After(time.Millisecond):
case <-tickChan:
t.Error("expected job fires exactly twice")
}
}
// Simple test using Runnables.
func TestJob(t *testing.T) {
wg := &sync.WaitGroup{}
wg.Add(1)
cron := New()
cron.AddJob("0 0 0 30 Feb ?", testJob{wg, "job0"})
cron.AddJob("0 0 0 1 1 ?", testJob{wg, "job1"})
cron.AddJob("* * * * * ?", testJob{wg, "job2"})
cron.AddJob("1 0 0 1 1 ?", testJob{wg, "job3"})
cron.Schedule(Every(5*time.Second+5*time.Nanosecond), testJob{wg, "job4"})
cron.Schedule(Every(5*time.Minute), testJob{wg, "job5"})
cron.Start()
defer cron.Stop()
select {
case <-time.After(OneSecond):
t.FailNow()
case <-wait(wg):
}
// Ensure the entries are in the right order.
expecteds := []string{"job2", "job4", "job5", "job1", "job3", "job0"}
var actuals []string
for _, entry := range cron.Entries() {
actuals = append(actuals, entry.Job.(testJob).name)
}
for i, expected := range expecteds {
if actuals[i] != expected {
t.Fatalf("Jobs not in the right order. (expected) %s != %s (actual)", expecteds, actuals)
}
}
}
type ZeroSchedule struct{}
func (*ZeroSchedule) Next(time.Time) time.Time {
return time.Time{}
}
// Tests that job without time does not run
func TestJobWithZeroTimeDoesNotRun(t *testing.T) {
cron := New()
calls := 0
cron.AddFunc("* * * * * *", func() { calls += 1 })
cron.Schedule(new(ZeroSchedule), FuncJob(func() { t.Error("expected zero task will not run") }))
cron.Start()
defer cron.Stop()
<-time.After(OneSecond)
if calls != 1 {
t.Errorf("called %d times, expected 1\n", calls)
}
}
func wait(wg *sync.WaitGroup) chan bool {
ch := make(chan bool)
go func() {
wg.Wait()
ch <- true
}()
return ch
}
func stop(cron *Cron) chan bool {
ch := make(chan bool)
go func() {
cron.Stop()
ch <- true
}()
return ch
}

View File

@ -1,129 +0,0 @@
/*
Package cron implements a cron spec parser and job runner.
Usage
Callers may register Funcs to be invoked on a given schedule. Cron will run
them in their own goroutines.
c := cron.New()
c.AddFunc("0 30 * * * *", func() { fmt.Println("Every hour on the half hour") })
c.AddFunc("@hourly", func() { fmt.Println("Every hour") })
c.AddFunc("@every 1h30m", func() { fmt.Println("Every hour thirty") })
c.Start()
..
// Funcs are invoked in their own goroutine, asynchronously.
...
// Funcs may also be added to a running Cron
c.AddFunc("@daily", func() { fmt.Println("Every day") })
..
// Inspect the cron job entries' next and previous run times.
inspect(c.Entries())
..
c.Stop() // Stop the scheduler (does not stop any jobs already running).
CRON Expression Format
A cron expression represents a set of times, using 6 space-separated fields.
Field name | Mandatory? | Allowed values | Allowed special characters
---------- | ---------- | -------------- | --------------------------
Seconds | Yes | 0-59 | * / , -
Minutes | Yes | 0-59 | * / , -
Hours | Yes | 0-23 | * / , -
Day of month | Yes | 1-31 | * / , - ?
Month | Yes | 1-12 or JAN-DEC | * / , -
Day of week | Yes | 0-6 or SUN-SAT | * / , - ?
Note: Month and Day-of-week field values are case insensitive. "SUN", "Sun",
and "sun" are equally accepted.
Special Characters
Asterisk ( * )
The asterisk indicates that the cron expression will match for all values of the
field; e.g., using an asterisk in the 5th field (month) would indicate every
month.
Slash ( / )
Slashes are used to describe increments of ranges. For example 3-59/15 in the
1st field (minutes) would indicate the 3rd minute of the hour and every 15
minutes thereafter. The form "*\/..." is equivalent to the form "first-last/...",
that is, an increment over the largest possible range of the field. The form
"N/..." is accepted as meaning "N-MAX/...", that is, starting at N, use the
increment until the end of that specific range. It does not wrap around.
Comma ( , )
Commas are used to separate items of a list. For example, using "MON,WED,FRI" in
the 5th field (day of week) would mean Mondays, Wednesdays and Fridays.
Hyphen ( - )
Hyphens are used to define ranges. For example, 9-17 would indicate every
hour between 9am and 5pm inclusive.
Question mark ( ? )
Question mark may be used instead of '*' for leaving either day-of-month or
day-of-week blank.
Predefined schedules
You may use one of several pre-defined schedules in place of a cron expression.
Entry | Description | Equivalent To
----- | ----------- | -------------
@yearly (or @annually) | Run once a year, midnight, Jan. 1st | 0 0 0 1 1 *
@monthly | Run once a month, midnight, first of month | 0 0 0 1 * *
@weekly | Run once a week, midnight between Sat/Sun | 0 0 0 * * 0
@daily (or @midnight) | Run once a day, midnight | 0 0 0 * * *
@hourly | Run once an hour, beginning of hour | 0 0 * * * *
Intervals
You may also schedule a job to execute at fixed intervals, starting at the time it's added
or cron is run. This is supported by formatting the cron spec like this:
@every <duration>
where "duration" is a string accepted by time.ParseDuration
(http://golang.org/pkg/time/#ParseDuration).
For example, "@every 1h30m10s" would indicate a schedule that activates after
1 hour, 30 minutes, 10 seconds, and then every interval after that.
Note: The interval does not take the job runtime into account. For example,
if a job takes 3 minutes to run, and it is scheduled to run every 5 minutes,
it will have only 2 minutes of idle time between each run.
Time zones
All interpretation and scheduling is done in the machine's local time zone (as
provided by the Go time package (http://www.golang.org/pkg/time).
Be aware that jobs scheduled during daylight-savings leap-ahead transitions will
not be run!
Thread safety
Since the Cron service runs concurrently with the calling code, some amount of
care must be taken to ensure proper synchronization.
All cron methods are designed to be correctly synchronized as long as the caller
ensures that invocations have a clear happens-before ordering between them.
Implementation
Cron entries are stored in an array, sorted by their next activation time. Cron
sleeps until the next job is due to be run.
Upon waking:
- it runs each entry that is active on that second
- it calculates the next run times for the jobs that were run
- it re-sorts the array of entries by next activation time.
- it goes to sleep until the soonest job.
*/
package cron

View File

@ -1,380 +0,0 @@
package cron
import (
"fmt"
"math"
"strconv"
"strings"
"time"
)
// Configuration options for creating a parser. Most options specify which
// fields should be included, while others enable features. If a field is not
// included the parser will assume a default value. These options do not change
// the order fields are parse in.
type ParseOption int
const (
Second ParseOption = 1 << iota // Seconds field, default 0
Minute // Minutes field, default 0
Hour // Hours field, default 0
Dom // Day of month field, default *
Month // Month field, default *
Dow // Day of week field, default *
DowOptional // Optional day of week field, default *
Descriptor // Allow descriptors such as @monthly, @weekly, etc.
)
var places = []ParseOption{
Second,
Minute,
Hour,
Dom,
Month,
Dow,
}
var defaults = []string{
"0",
"0",
"0",
"*",
"*",
"*",
}
// A custom Parser that can be configured.
type Parser struct {
options ParseOption
optionals int
}
// Creates a custom Parser with custom options.
//
// // Standard parser without descriptors
// specParser := NewParser(Minute | Hour | Dom | Month | Dow)
// sched, err := specParser.Parse("0 0 15 */3 *")
//
// // Same as above, just excludes time fields
// subsParser := NewParser(Dom | Month | Dow)
// sched, err := specParser.Parse("15 */3 *")
//
// // Same as above, just makes Dow optional
// subsParser := NewParser(Dom | Month | DowOptional)
// sched, err := specParser.Parse("15 */3")
//
func NewParser(options ParseOption) Parser {
optionals := 0
if options&DowOptional > 0 {
options |= Dow
optionals++
}
return Parser{options, optionals}
}
// Parse returns a new crontab schedule representing the given spec.
// It returns a descriptive error if the spec is not valid.
// It accepts crontab specs and features configured by NewParser.
func (p Parser) Parse(spec string) (Schedule, error) {
if len(spec) == 0 {
return nil, fmt.Errorf("Empty spec string")
}
if spec[0] == '@' && p.options&Descriptor > 0 {
return parseDescriptor(spec)
}
// Figure out how many fields we need
max := 0
for _, place := range places {
if p.options&place > 0 {
max++
}
}
min := max - p.optionals
// Split fields on whitespace
fields := strings.Fields(spec)
// Validate number of fields
if count := len(fields); count < min || count > max {
if min == max {
return nil, fmt.Errorf("Expected exactly %d fields, found %d: %s", min, count, spec)
}
return nil, fmt.Errorf("Expected %d to %d fields, found %d: %s", min, max, count, spec)
}
// Fill in missing fields
fields = expandFields(fields, p.options)
var err error
field := func(field string, r bounds) uint64 {
if err != nil {
return 0
}
var bits uint64
bits, err = getField(field, r)
return bits
}
var (
second = field(fields[0], seconds)
minute = field(fields[1], minutes)
hour = field(fields[2], hours)
dayofmonth = field(fields[3], dom)
month = field(fields[4], months)
dayofweek = field(fields[5], dow)
)
if err != nil {
return nil, err
}
return &SpecSchedule{
Second: second,
Minute: minute,
Hour: hour,
Dom: dayofmonth,
Month: month,
Dow: dayofweek,
}, nil
}
func expandFields(fields []string, options ParseOption) []string {
n := 0
count := len(fields)
expFields := make([]string, len(places))
copy(expFields, defaults)
for i, place := range places {
if options&place > 0 {
expFields[i] = fields[n]
n++
}
if n == count {
break
}
}
return expFields
}
var standardParser = NewParser(
Minute | Hour | Dom | Month | Dow | Descriptor,
)
// ParseStandard returns a new crontab schedule representing the given standardSpec
// (https://en.wikipedia.org/wiki/Cron). It differs from Parse requiring to always
// pass 5 entries representing: minute, hour, day of month, month and day of week,
// in that order. It returns a descriptive error if the spec is not valid.
//
// It accepts
// - Standard crontab specs, e.g. "* * * * ?"
// - Descriptors, e.g. "@midnight", "@every 1h30m"
func ParseStandard(standardSpec string) (Schedule, error) {
return standardParser.Parse(standardSpec)
}
var defaultParser = NewParser(
Second | Minute | Hour | Dom | Month | DowOptional | Descriptor,
)
// Parse returns a new crontab schedule representing the given spec.
// It returns a descriptive error if the spec is not valid.
//
// It accepts
// - Full crontab specs, e.g. "* * * * * ?"
// - Descriptors, e.g. "@midnight", "@every 1h30m"
func Parse(spec string) (Schedule, error) {
return defaultParser.Parse(spec)
}
// getField returns an Int with the bits set representing all of the times that
// the field represents or error parsing field value. A "field" is a comma-separated
// list of "ranges".
func getField(field string, r bounds) (uint64, error) {
var bits uint64
ranges := strings.FieldsFunc(field, func(r rune) bool { return r == ',' })
for _, expr := range ranges {
bit, err := getRange(expr, r)
if err != nil {
return bits, err
}
bits |= bit
}
return bits, nil
}
// getRange returns the bits indicated by the given expression:
// number | number "-" number [ "/" number ]
// or error parsing range.
func getRange(expr string, r bounds) (uint64, error) {
var (
start, end, step uint
rangeAndStep = strings.Split(expr, "/")
lowAndHigh = strings.Split(rangeAndStep[0], "-")
singleDigit = len(lowAndHigh) == 1
err error
)
var extra uint64
if lowAndHigh[0] == "*" || lowAndHigh[0] == "?" {
start = r.min
end = r.max
extra = starBit
} else {
start, err = parseIntOrName(lowAndHigh[0], r.names)
if err != nil {
return 0, err
}
switch len(lowAndHigh) {
case 1:
end = start
case 2:
end, err = parseIntOrName(lowAndHigh[1], r.names)
if err != nil {
return 0, err
}
default:
return 0, fmt.Errorf("Too many hyphens: %s", expr)
}
}
switch len(rangeAndStep) {
case 1:
step = 1
case 2:
step, err = mustParseInt(rangeAndStep[1])
if err != nil {
return 0, err
}
// Special handling: "N/step" means "N-max/step".
if singleDigit {
end = r.max
}
default:
return 0, fmt.Errorf("Too many slashes: %s", expr)
}
if start < r.min {
return 0, fmt.Errorf("Beginning of range (%d) below minimum (%d): %s", start, r.min, expr)
}
if end > r.max {
return 0, fmt.Errorf("End of range (%d) above maximum (%d): %s", end, r.max, expr)
}
if start > end {
return 0, fmt.Errorf("Beginning of range (%d) beyond end of range (%d): %s", start, end, expr)
}
if step == 0 {
return 0, fmt.Errorf("Step of range should be a positive number: %s", expr)
}
return getBits(start, end, step) | extra, nil
}
// parseIntOrName returns the (possibly-named) integer contained in expr.
func parseIntOrName(expr string, names map[string]uint) (uint, error) {
if names != nil {
if namedInt, ok := names[strings.ToLower(expr)]; ok {
return namedInt, nil
}
}
return mustParseInt(expr)
}
// mustParseInt parses the given expression as an int or returns an error.
func mustParseInt(expr string) (uint, error) {
num, err := strconv.Atoi(expr)
if err != nil {
return 0, fmt.Errorf("Failed to parse int from %s: %s", expr, err)
}
if num < 0 {
return 0, fmt.Errorf("Negative number (%d) not allowed: %s", num, expr)
}
return uint(num), nil
}
// getBits sets all bits in the range [min, max], modulo the given step size.
func getBits(min, max, step uint) uint64 {
var bits uint64
// If step is 1, use shifts.
if step == 1 {
return ^(math.MaxUint64 << (max + 1)) & (math.MaxUint64 << min)
}
// Else, use a simple loop.
for i := min; i <= max; i += step {
bits |= 1 << i
}
return bits
}
// all returns all bits within the given bounds. (plus the star bit)
func all(r bounds) uint64 {
return getBits(r.min, r.max, 1) | starBit
}
// parseDescriptor returns a predefined schedule for the expression, or error if none matches.
func parseDescriptor(descriptor string) (Schedule, error) {
switch descriptor {
case "@yearly", "@annually":
return &SpecSchedule{
Second: 1 << seconds.min,
Minute: 1 << minutes.min,
Hour: 1 << hours.min,
Dom: 1 << dom.min,
Month: 1 << months.min,
Dow: all(dow),
}, nil
case "@monthly":
return &SpecSchedule{
Second: 1 << seconds.min,
Minute: 1 << minutes.min,
Hour: 1 << hours.min,
Dom: 1 << dom.min,
Month: all(months),
Dow: all(dow),
}, nil
case "@weekly":
return &SpecSchedule{
Second: 1 << seconds.min,
Minute: 1 << minutes.min,
Hour: 1 << hours.min,
Dom: all(dom),
Month: all(months),
Dow: 1 << dow.min,
}, nil
case "@daily", "@midnight":
return &SpecSchedule{
Second: 1 << seconds.min,
Minute: 1 << minutes.min,
Hour: 1 << hours.min,
Dom: all(dom),
Month: all(months),
Dow: all(dow),
}, nil
case "@hourly":
return &SpecSchedule{
Second: 1 << seconds.min,
Minute: 1 << minutes.min,
Hour: all(hours),
Dom: all(dom),
Month: all(months),
Dow: all(dow),
}, nil
}
const every = "@every "
if strings.HasPrefix(descriptor, every) {
duration, err := time.ParseDuration(descriptor[len(every):])
if err != nil {
return nil, fmt.Errorf("Failed to parse duration %s: %s", descriptor, err)
}
return Every(duration), nil
}
return nil, fmt.Errorf("Unrecognized descriptor: %s", descriptor)
}

View File

@ -1,234 +0,0 @@
package cron
import (
"reflect"
"strings"
"testing"
"time"
)
func TestRange(t *testing.T) {
zero := uint64(0)
ranges := []struct {
expr string
min, max uint
expected uint64
err string
}{
{"5", 0, 7, 1 << 5, ""},
{"0", 0, 7, 1 << 0, ""},
{"7", 0, 7, 1 << 7, ""},
{"5-5", 0, 7, 1 << 5, ""},
{"5-6", 0, 7, 1<<5 | 1<<6, ""},
{"5-7", 0, 7, 1<<5 | 1<<6 | 1<<7, ""},
{"5-6/2", 0, 7, 1 << 5, ""},
{"5-7/2", 0, 7, 1<<5 | 1<<7, ""},
{"5-7/1", 0, 7, 1<<5 | 1<<6 | 1<<7, ""},
{"*", 1, 3, 1<<1 | 1<<2 | 1<<3 | starBit, ""},
{"*/2", 1, 3, 1<<1 | 1<<3 | starBit, ""},
{"5--5", 0, 0, zero, "Too many hyphens"},
{"jan-x", 0, 0, zero, "Failed to parse int from"},
{"2-x", 1, 5, zero, "Failed to parse int from"},
{"*/-12", 0, 0, zero, "Negative number"},
{"*//2", 0, 0, zero, "Too many slashes"},
{"1", 3, 5, zero, "below minimum"},
{"6", 3, 5, zero, "above maximum"},
{"5-3", 3, 5, zero, "beyond end of range"},
{"*/0", 0, 0, zero, "should be a positive number"},
}
for _, c := range ranges {
actual, err := getRange(c.expr, bounds{c.min, c.max, nil})
if len(c.err) != 0 && (err == nil || !strings.Contains(err.Error(), c.err)) {
t.Errorf("%s => expected %v, got %v", c.expr, c.err, err)
}
if len(c.err) == 0 && err != nil {
t.Errorf("%s => unexpected error %v", c.expr, err)
}
if actual != c.expected {
t.Errorf("%s => expected %d, got %d", c.expr, c.expected, actual)
}
}
}
func TestField(t *testing.T) {
fields := []struct {
expr string
min, max uint
expected uint64
}{
{"5", 1, 7, 1 << 5},
{"5,6", 1, 7, 1<<5 | 1<<6},
{"5,6,7", 1, 7, 1<<5 | 1<<6 | 1<<7},
{"1,5-7/2,3", 1, 7, 1<<1 | 1<<5 | 1<<7 | 1<<3},
}
for _, c := range fields {
actual, _ := getField(c.expr, bounds{c.min, c.max, nil})
if actual != c.expected {
t.Errorf("%s => expected %d, got %d", c.expr, c.expected, actual)
}
}
}
func TestAll(t *testing.T) {
allBits := []struct {
r bounds
expected uint64
}{
{minutes, 0xfffffffffffffff}, // 0-59: 60 ones
{hours, 0xffffff}, // 0-23: 24 ones
{dom, 0xfffffffe}, // 1-31: 31 ones, 1 zero
{months, 0x1ffe}, // 1-12: 12 ones, 1 zero
{dow, 0x7f}, // 0-6: 7 ones
}
for _, c := range allBits {
actual := all(c.r) // all() adds the starBit, so compensate for that..
if c.expected|starBit != actual {
t.Errorf("%d-%d/%d => expected %b, got %b",
c.r.min, c.r.max, 1, c.expected|starBit, actual)
}
}
}
func TestBits(t *testing.T) {
bits := []struct {
min, max, step uint
expected uint64
}{
{0, 0, 1, 0x1},
{1, 1, 1, 0x2},
{1, 5, 2, 0x2a}, // 101010
{1, 4, 2, 0xa}, // 1010
}
for _, c := range bits {
actual := getBits(c.min, c.max, c.step)
if c.expected != actual {
t.Errorf("%d-%d/%d => expected %b, got %b",
c.min, c.max, c.step, c.expected, actual)
}
}
}
func TestParse(t *testing.T) {
entries := []struct {
expr string
expected Schedule
err string
}{
{
expr: "* 5 * * * *",
expected: &SpecSchedule{
Second: all(seconds),
Minute: 1 << 5,
Hour: all(hours),
Dom: all(dom),
Month: all(months),
Dow: all(dow),
},
},
{
expr: "* 5 j * * *",
err: "Failed to parse int from",
},
{
expr: "@every 5m",
expected: ConstantDelaySchedule{Delay: time.Duration(5) * time.Minute},
},
{
expr: "@every Xm",
err: "Failed to parse duration",
},
{
expr: "@yearly",
expected: &SpecSchedule{
Second: 1 << seconds.min,
Minute: 1 << minutes.min,
Hour: 1 << hours.min,
Dom: 1 << dom.min,
Month: 1 << months.min,
Dow: all(dow),
},
},
{
expr: "@annually",
expected: &SpecSchedule{
Second: 1 << seconds.min,
Minute: 1 << minutes.min,
Hour: 1 << hours.min,
Dom: 1 << dom.min,
Month: 1 << months.min,
Dow: all(dow),
},
},
{
expr: "@unrecognized",
err: "Unrecognized descriptor",
},
{
expr: "* * * *",
err: "Expected 5 to 6 fields",
},
{
expr: "",
err: "Empty spec string",
},
}
for _, c := range entries {
actual, err := Parse(c.expr)
if len(c.err) != 0 && (err == nil || !strings.Contains(err.Error(), c.err)) {
t.Errorf("%s => expected %v, got %v", c.expr, c.err, err)
}
if len(c.err) == 0 && err != nil {
t.Errorf("%s => unexpected error %v", c.expr, err)
}
if !reflect.DeepEqual(actual, c.expected) {
t.Errorf("%s => expected %b, got %b", c.expr, c.expected, actual)
}
}
}
func TestStandardSpecSchedule(t *testing.T) {
entries := []struct {
expr string
expected Schedule
err string
}{
{
expr: "5 * * * *",
expected: &SpecSchedule{1 << seconds.min, 1 << 5, all(hours), all(dom), all(months), all(dow)},
},
{
expr: "@every 5m",
expected: ConstantDelaySchedule{time.Duration(5) * time.Minute},
},
{
expr: "5 j * * *",
err: "Failed to parse int from",
},
{
expr: "* * * *",
err: "Expected exactly 5 fields",
},
}
for _, c := range entries {
actual, err := ParseStandard(c.expr)
if len(c.err) != 0 && (err == nil || !strings.Contains(err.Error(), c.err)) {
t.Errorf("%s => expected %v, got %v", c.expr, c.err, err)
}
if len(c.err) == 0 && err != nil {
t.Errorf("%s => unexpected error %v", c.expr, err)
}
if !reflect.DeepEqual(actual, c.expected) {
t.Errorf("%s => expected %b, got %b", c.expr, c.expected, actual)
}
}
}

View File

@ -1,158 +0,0 @@
package cron
import "time"
// SpecSchedule specifies a duty cycle (to the second granularity), based on a
// traditional crontab specification. It is computed initially and stored as bit sets.
type SpecSchedule struct {
Second, Minute, Hour, Dom, Month, Dow uint64
}
// bounds provides a range of acceptable values (plus a map of name to value).
type bounds struct {
min, max uint
names map[string]uint
}
// The bounds for each field.
var (
seconds = bounds{0, 59, nil}
minutes = bounds{0, 59, nil}
hours = bounds{0, 23, nil}
dom = bounds{1, 31, nil}
months = bounds{1, 12, map[string]uint{
"jan": 1,
"feb": 2,
"mar": 3,
"apr": 4,
"may": 5,
"jun": 6,
"jul": 7,
"aug": 8,
"sep": 9,
"oct": 10,
"nov": 11,
"dec": 12,
}}
dow = bounds{0, 6, map[string]uint{
"sun": 0,
"mon": 1,
"tue": 2,
"wed": 3,
"thu": 4,
"fri": 5,
"sat": 6,
}}
)
const (
// Set the top bit if a star was included in the expression.
starBit = 1 << 63
)
// Next returns the next time this schedule is activated, greater than the given
// time. If no time can be found to satisfy the schedule, return the zero time.
func (s *SpecSchedule) Next(t time.Time) time.Time {
// General approach:
// For Month, Day, Hour, Minute, Second:
// Check if the time value matches. If yes, continue to the next field.
// If the field doesn't match the schedule, then increment the field until it matches.
// While incrementing the field, a wrap-around brings it back to the beginning
// of the field list (since it is necessary to re-verify previous field
// values)
// Start at the earliest possible time (the upcoming second).
t = t.Add(1*time.Second - time.Duration(t.Nanosecond())*time.Nanosecond)
// This flag indicates whether a field has been incremented.
added := false
// If no time is found within five years, return zero.
yearLimit := t.Year() + 5
WRAP:
if t.Year() > yearLimit {
return time.Time{}
}
// Find the first applicable month.
// If it's this month, then do nothing.
for 1<<uint(t.Month())&s.Month == 0 {
// If we have to add a month, reset the other parts to 0.
if !added {
added = true
// Otherwise, set the date at the beginning (since the current time is irrelevant).
t = time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location())
}
t = t.AddDate(0, 1, 0)
// Wrapped around.
if t.Month() == time.January {
goto WRAP
}
}
// Now get a day in that month.
for !dayMatches(s, t) {
if !added {
added = true
t = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
}
t = t.AddDate(0, 0, 1)
if t.Day() == 1 {
goto WRAP
}
}
for 1<<uint(t.Hour())&s.Hour == 0 {
if !added {
added = true
t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), 0, 0, 0, t.Location())
}
t = t.Add(1 * time.Hour)
if t.Hour() == 0 {
goto WRAP
}
}
for 1<<uint(t.Minute())&s.Minute == 0 {
if !added {
added = true
t = t.Truncate(time.Minute)
}
t = t.Add(1 * time.Minute)
if t.Minute() == 0 {
goto WRAP
}
}
for 1<<uint(t.Second())&s.Second == 0 {
if !added {
added = true
t = t.Truncate(time.Second)
}
t = t.Add(1 * time.Second)
if t.Second() == 0 {
goto WRAP
}
}
return t
}
// dayMatches returns true if the schedule's day-of-week and day-of-month
// restrictions are satisfied by the given time.
func dayMatches(s *SpecSchedule, t time.Time) bool {
var (
domMatch bool = 1<<uint(t.Day())&s.Dom > 0
dowMatch bool = 1<<uint(t.Weekday())&s.Dow > 0
)
if s.Dom&starBit > 0 || s.Dow&starBit > 0 {
return domMatch && dowMatch
}
return domMatch || dowMatch
}

View File

@ -1,249 +0,0 @@
package cron
import (
"testing"
"time"
)
func TestActivation(t *testing.T) {
tests := []struct {
time, spec string
expected bool
}{
// Every fifteen minutes.
{"Mon Jul 9 15:00 2012", "0 0/15 * * *", true},
{"Mon Jul 9 15:45 2012", "0 0/15 * * *", true},
{"Mon Jul 9 15:40 2012", "0 0/15 * * *", false},
// Every fifteen minutes, starting at 5 minutes.
{"Mon Jul 9 15:05 2012", "0 5/15 * * *", true},
{"Mon Jul 9 15:20 2012", "0 5/15 * * *", true},
{"Mon Jul 9 15:50 2012", "0 5/15 * * *", true},
// Named months
{"Sun Jul 15 15:00 2012", "0 0/15 * * Jul", true},
{"Sun Jul 15 15:00 2012", "0 0/15 * * Jun", false},
// Everything set.
{"Sun Jul 15 08:30 2012", "0 30 08 ? Jul Sun", true},
{"Sun Jul 15 08:30 2012", "0 30 08 15 Jul ?", true},
{"Mon Jul 16 08:30 2012", "0 30 08 ? Jul Sun", false},
{"Mon Jul 16 08:30 2012", "0 30 08 15 Jul ?", false},
// Predefined schedules
{"Mon Jul 9 15:00 2012", "@hourly", true},
{"Mon Jul 9 15:04 2012", "@hourly", false},
{"Mon Jul 9 15:00 2012", "@daily", false},
{"Mon Jul 9 00:00 2012", "@daily", true},
{"Mon Jul 9 00:00 2012", "@weekly", false},
{"Sun Jul 8 00:00 2012", "@weekly", true},
{"Sun Jul 8 01:00 2012", "@weekly", false},
{"Sun Jul 8 00:00 2012", "@monthly", false},
{"Sun Jul 1 00:00 2012", "@monthly", true},
// Test interaction of DOW and DOM.
// If both are specified, then only one needs to match.
{"Sun Jul 15 00:00 2012", "0 * * 1,15 * Sun", true},
{"Fri Jun 15 00:00 2012", "0 * * 1,15 * Sun", true},
{"Wed Aug 1 00:00 2012", "0 * * 1,15 * Sun", true},
// However, if one has a star, then both need to match.
{"Sun Jul 15 00:00 2012", "0 * * * * Mon", false},
{"Sun Jul 15 00:00 2012", "0 * * */10 * Sun", false},
{"Mon Jul 9 00:00 2012", "0 * * 1,15 * *", false},
{"Sun Jul 15 00:00 2012", "0 * * 1,15 * *", true},
{"Sun Jul 15 00:00 2012", "0 * * */2 * Sun", true},
}
for _, test := range tests {
sched, err := Parse(test.spec)
if err != nil {
t.Error(err)
continue
}
actual := sched.Next(getTime(test.time).Add(-1 * time.Second))
expected := getTime(test.time)
if test.expected && expected != actual || !test.expected && expected == actual {
t.Errorf("Fail evaluating %s on %s: (expected) %s != %s (actual)",
test.spec, test.time, expected, actual)
}
}
}
func TestNext(t *testing.T) {
runs := []struct {
time, spec string
expected string
}{
// Simple cases
{"Mon Jul 9 14:45 2012", "0 0/15 * * *", "Mon Jul 9 15:00 2012"},
{"Mon Jul 9 14:59 2012", "0 0/15 * * *", "Mon Jul 9 15:00 2012"},
{"Mon Jul 9 14:59:59 2012", "0 0/15 * * *", "Mon Jul 9 15:00 2012"},
// Wrap around hours
{"Mon Jul 9 15:45 2012", "0 20-35/15 * * *", "Mon Jul 9 16:20 2012"},
// Wrap around days
{"Mon Jul 9 23:46 2012", "0 */15 * * *", "Tue Jul 10 00:00 2012"},
{"Mon Jul 9 23:45 2012", "0 20-35/15 * * *", "Tue Jul 10 00:20 2012"},
{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * * *", "Tue Jul 10 00:20:15 2012"},
{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 1/2 * *", "Tue Jul 10 01:20:15 2012"},
{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 10-12 * *", "Tue Jul 10 10:20:15 2012"},
{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 1/2 */2 * *", "Thu Jul 11 01:20:15 2012"},
{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * 9-20 * *", "Wed Jul 10 00:20:15 2012"},
{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * 9-20 Jul *", "Wed Jul 10 00:20:15 2012"},
// Wrap around months
{"Mon Jul 9 23:35 2012", "0 0 0 9 Apr-Oct ?", "Thu Aug 9 00:00 2012"},
{"Mon Jul 9 23:35 2012", "0 0 0 */5 Apr,Aug,Oct Mon", "Mon Aug 6 00:00 2012"},
{"Mon Jul 9 23:35 2012", "0 0 0 */5 Oct Mon", "Mon Oct 1 00:00 2012"},
// Wrap around years
{"Mon Jul 9 23:35 2012", "0 0 0 * Feb Mon", "Mon Feb 4 00:00 2013"},
{"Mon Jul 9 23:35 2012", "0 0 0 * Feb Mon/2", "Fri Feb 1 00:00 2013"},
// Wrap around minute, hour, day, month, and year
{"Mon Dec 31 23:59:45 2012", "0 * * * * *", "Tue Jan 1 00:00:00 2013"},
// Leap year
{"Mon Jul 9 23:35 2012", "0 0 0 29 Feb ?", "Mon Feb 29 00:00 2016"},
// Daylight savings time 2am EST (-5) -> 3am EDT (-4)
{"2012-03-11T00:00:00-0500", "0 30 2 11 Mar ?", "2013-03-11T02:30:00-0400"},
// hourly job
{"2012-03-11T00:00:00-0500", "0 0 * * * ?", "2012-03-11T01:00:00-0500"},
{"2012-03-11T01:00:00-0500", "0 0 * * * ?", "2012-03-11T03:00:00-0400"},
{"2012-03-11T03:00:00-0400", "0 0 * * * ?", "2012-03-11T04:00:00-0400"},
{"2012-03-11T04:00:00-0400", "0 0 * * * ?", "2012-03-11T05:00:00-0400"},
// 1am nightly job
{"2012-03-11T00:00:00-0500", "0 0 1 * * ?", "2012-03-11T01:00:00-0500"},
{"2012-03-11T01:00:00-0500", "0 0 1 * * ?", "2012-03-12T01:00:00-0400"},
// 2am nightly job (skipped)
{"2012-03-11T00:00:00-0500", "0 0 2 * * ?", "2012-03-12T02:00:00-0400"},
// Daylight savings time 2am EDT (-4) => 1am EST (-5)
{"2012-11-04T00:00:00-0400", "0 30 2 04 Nov ?", "2012-11-04T02:30:00-0500"},
{"2012-11-04T01:45:00-0400", "0 30 1 04 Nov ?", "2012-11-04T01:30:00-0500"},
// hourly job
{"2012-11-04T00:00:00-0400", "0 0 * * * ?", "2012-11-04T01:00:00-0400"},
{"2012-11-04T01:00:00-0400", "0 0 * * * ?", "2012-11-04T01:00:00-0500"},
{"2012-11-04T01:00:00-0500", "0 0 * * * ?", "2012-11-04T02:00:00-0500"},
// 1am nightly job (runs twice)
{"2012-11-04T00:00:00-0400", "0 0 1 * * ?", "2012-11-04T01:00:00-0400"},
{"2012-11-04T01:00:00-0400", "0 0 1 * * ?", "2012-11-04T01:00:00-0500"},
{"2012-11-04T01:00:00-0500", "0 0 1 * * ?", "2012-11-05T01:00:00-0500"},
// 2am nightly job
{"2012-11-04T00:00:00-0400", "0 0 2 * * ?", "2012-11-04T02:00:00-0500"},
{"2012-11-04T02:00:00-0500", "0 0 2 * * ?", "2012-11-05T02:00:00-0500"},
// 3am nightly job
{"2012-11-04T00:00:00-0400", "0 0 3 * * ?", "2012-11-04T03:00:00-0500"},
{"2012-11-04T03:00:00-0500", "0 0 3 * * ?", "2012-11-05T03:00:00-0500"},
// Unsatisfiable
{"Mon Jul 9 23:35 2012", "0 0 0 30 Feb ?", ""},
{"Mon Jul 9 23:35 2012", "0 0 0 31 Apr ?", ""},
}
for _, c := range runs {
sched, err := Parse(c.spec)
if err != nil {
t.Error(err)
continue
}
actual := sched.Next(getTime(c.time))
expected := getTime(c.expected)
if !actual.Equal(expected) {
t.Errorf("%s, \"%s\": (expected) %v != %v (actual)", c.time, c.spec, expected, actual)
}
}
}
func TestErrors(t *testing.T) {
invalidSpecs := []string{
"xyz",
"60 0 * * *",
"0 60 * * *",
"0 0 * * XYZ",
}
for _, spec := range invalidSpecs {
_, err := Parse(spec)
if err == nil {
t.Error("expected an error parsing: ", spec)
}
}
}
func getTime(value string) time.Time {
if value == "" {
return time.Time{}
}
t, err := time.Parse("Mon Jan 2 15:04 2006", value)
if err != nil {
t, err = time.Parse("Mon Jan 2 15:04:05 2006", value)
if err != nil {
t, err = time.Parse("2006-01-02T15:04:05-0700", value)
if err != nil {
panic(err)
}
// Daylight savings time tests require location
if ny, err := time.LoadLocation("America/New_York"); err == nil {
t = t.In(ny)
}
}
}
return t
}
func TestNextWithTz(t *testing.T) {
runs := []struct {
time, spec string
expected string
}{
// Failing tests
{"2016-01-03T13:09:03+0530", "0 14 14 * * *", "2016-01-03T14:14:00+0530"},
{"2016-01-03T04:09:03+0530", "0 14 14 * * ?", "2016-01-03T14:14:00+0530"},
// Passing tests
{"2016-01-03T14:09:03+0530", "0 14 14 * * *", "2016-01-03T14:14:00+0530"},
{"2016-01-03T14:00:00+0530", "0 14 14 * * ?", "2016-01-03T14:14:00+0530"},
}
for _, c := range runs {
sched, err := Parse(c.spec)
if err != nil {
t.Error(err)
continue
}
actual := sched.Next(getTimeTZ(c.time))
expected := getTimeTZ(c.expected)
if !actual.Equal(expected) {
t.Errorf("%s, \"%s\": (expected) %v != %v (actual)", c.time, c.spec, expected, actual)
}
}
}
func getTimeTZ(value string) time.Time {
if value == "" {
return time.Time{}
}
t, err := time.Parse("Mon Jan 2 15:04 2006", value)
if err != nil {
t, err = time.Parse("Mon Jan 2 15:04:05 2006", value)
if err != nil {
t, err = time.Parse("2006-01-02T15:04:05-0700", value)
if err != nil {
panic(err)
}
}
}
return t
}

View File

@ -1,4 +0,0 @@
_*
*.swp
*.[568]
[568].out

View File

@ -1,3 +0,0 @@
language: go
go_import_path: gopkg.in/check.v1

View File

@ -1,25 +0,0 @@
Gocheck - A rich testing framework for Go
Copyright (c) 2010-2013 Gustavo Niemeyer <gustavo@niemeyer.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,20 +0,0 @@
Instructions
============
Install the package with:
go get gopkg.in/check.v1
Import it with:
import "gitee.com/johng/gf/third/gopkg.in/check.v1"
and use _check_ as the package name inside the code.
For more details, visit the project page:
* http://labix.org/gocheck
and the API documentation:
* https://gopkg.in/check.v1

View File

@ -1,2 +0,0 @@
- Assert(slice, Contains, item)
- Parallel test support

View File

@ -1,187 +0,0 @@
// Copyright (c) 2012 The Go Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package check
import (
"fmt"
"runtime"
"time"
)
var memStats runtime.MemStats
// testingB is a type passed to Benchmark functions to manage benchmark
// timing and to specify the number of iterations to run.
type timer struct {
start time.Time // Time test or benchmark started
duration time.Duration
N int
bytes int64
timerOn bool
benchTime time.Duration
// The initial states of memStats.Mallocs and memStats.TotalAlloc.
startAllocs uint64
startBytes uint64
// The net total of this test after being run.
netAllocs uint64
netBytes uint64
}
// StartTimer starts timing a test. This function is called automatically
// before a benchmark starts, but it can also used to resume timing after
// a call to StopTimer.
func (c *C) StartTimer() {
if !c.timerOn {
c.start = time.Now()
c.timerOn = true
runtime.ReadMemStats(&memStats)
c.startAllocs = memStats.Mallocs
c.startBytes = memStats.TotalAlloc
}
}
// StopTimer stops timing a test. This can be used to pause the timer
// while performing complex initialization that you don't
// want to measure.
func (c *C) StopTimer() {
if c.timerOn {
c.duration += time.Now().Sub(c.start)
c.timerOn = false
runtime.ReadMemStats(&memStats)
c.netAllocs += memStats.Mallocs - c.startAllocs
c.netBytes += memStats.TotalAlloc - c.startBytes
}
}
// ResetTimer sets the elapsed benchmark time to zero.
// It does not affect whether the timer is running.
func (c *C) ResetTimer() {
if c.timerOn {
c.start = time.Now()
runtime.ReadMemStats(&memStats)
c.startAllocs = memStats.Mallocs
c.startBytes = memStats.TotalAlloc
}
c.duration = 0
c.netAllocs = 0
c.netBytes = 0
}
// SetBytes informs the number of bytes that the benchmark processes
// on each iteration. If this is called in a benchmark it will also
// report MB/s.
func (c *C) SetBytes(n int64) {
c.bytes = n
}
func (c *C) nsPerOp() int64 {
if c.N <= 0 {
return 0
}
return c.duration.Nanoseconds() / int64(c.N)
}
func (c *C) mbPerSec() float64 {
if c.bytes <= 0 || c.duration <= 0 || c.N <= 0 {
return 0
}
return (float64(c.bytes) * float64(c.N) / 1e6) / c.duration.Seconds()
}
func (c *C) timerString() string {
if c.N <= 0 {
return fmt.Sprintf("%3.3fs", float64(c.duration.Nanoseconds())/1e9)
}
mbs := c.mbPerSec()
mb := ""
if mbs != 0 {
mb = fmt.Sprintf("\t%7.2f MB/s", mbs)
}
nsop := c.nsPerOp()
ns := fmt.Sprintf("%10d ns/op", nsop)
if c.N > 0 && nsop < 100 {
// The format specifiers here make sure that
// the ones digits line up for all three possible formats.
if nsop < 10 {
ns = fmt.Sprintf("%13.2f ns/op", float64(c.duration.Nanoseconds())/float64(c.N))
} else {
ns = fmt.Sprintf("%12.1f ns/op", float64(c.duration.Nanoseconds())/float64(c.N))
}
}
memStats := ""
if c.benchMem {
allocedBytes := fmt.Sprintf("%8d B/op", int64(c.netBytes)/int64(c.N))
allocs := fmt.Sprintf("%8d allocs/op", int64(c.netAllocs)/int64(c.N))
memStats = fmt.Sprintf("\t%s\t%s", allocedBytes, allocs)
}
return fmt.Sprintf("%8d\t%s%s%s", c.N, ns, mb, memStats)
}
func min(x, y int) int {
if x > y {
return y
}
return x
}
func max(x, y int) int {
if x < y {
return y
}
return x
}
// roundDown10 rounds a number down to the nearest power of 10.
func roundDown10(n int) int {
var tens = 0
// tens = floor(log_10(n))
for n > 10 {
n = n / 10
tens++
}
// result = 10^tens
result := 1
for i := 0; i < tens; i++ {
result *= 10
}
return result
}
// roundUp rounds x up to a number of the form [1eX, 2eX, 5eX].
func roundUp(n int) int {
base := roundDown10(n)
if n < (2 * base) {
return 2 * base
}
if n < (5 * base) {
return 5 * base
}
return 10 * base
}

View File

@ -1,91 +0,0 @@
// These tests verify the test running logic.
package check_test
import (
"time"
. "gitee.com/johng/gf/third/gopkg.in/check.v1"
)
var benchmarkS = Suite(&BenchmarkS{})
type BenchmarkS struct{}
func (s *BenchmarkS) TestCountSuite(c *C) {
suitesRun += 1
}
func (s *BenchmarkS) TestBasicTestTiming(c *C) {
helper := FixtureHelper{sleepOn: "Test1", sleep: 1000000 * time.Nanosecond}
output := String{}
runConf := RunConf{Output: &output, Verbose: true}
Run(&helper, &runConf)
expected := "PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Test1\t0\\.0[0-9]+s\n" +
"PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Test2\t0\\.0[0-9]+s\n"
c.Assert(output.value, Matches, expected)
}
func (s *BenchmarkS) TestStreamTestTiming(c *C) {
helper := FixtureHelper{sleepOn: "SetUpSuite", sleep: 1000000 * time.Nanosecond}
output := String{}
runConf := RunConf{Output: &output, Stream: true}
Run(&helper, &runConf)
expected := "(?s).*\nPASS: check_test\\.go:[0-9]+: FixtureHelper\\.SetUpSuite\t[0-9]+\\.[0-9]+s\n.*"
c.Assert(output.value, Matches, expected)
}
func (s *BenchmarkS) TestBenchmark(c *C) {
helper := FixtureHelper{sleep: 100000}
output := String{}
runConf := RunConf{
Output: &output,
Benchmark: true,
BenchmarkTime: 10000000,
Filter: "Benchmark1",
}
Run(&helper, &runConf)
c.Check(helper.calls[0], Equals, "SetUpSuite")
c.Check(helper.calls[1], Equals, "SetUpTest")
c.Check(helper.calls[2], Equals, "Benchmark1")
c.Check(helper.calls[3], Equals, "TearDownTest")
c.Check(helper.calls[4], Equals, "SetUpTest")
c.Check(helper.calls[5], Equals, "Benchmark1")
c.Check(helper.calls[6], Equals, "TearDownTest")
// ... and more.
expected := "PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Benchmark1\t\\s+[0-9]+\t\\s+[0-9]+ ns/op\n"
c.Assert(output.value, Matches, expected)
}
func (s *BenchmarkS) TestBenchmarkBytes(c *C) {
helper := FixtureHelper{sleep: 100000}
output := String{}
runConf := RunConf{
Output: &output,
Benchmark: true,
BenchmarkTime: 10000000,
Filter: "Benchmark2",
}
Run(&helper, &runConf)
expected := "PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Benchmark2\t\\s+[0-9]+\t\\s+[0-9]+ ns/op\t\\s+ *[1-9]\\.[0-9]{2} MB/s\n"
c.Assert(output.value, Matches, expected)
}
func (s *BenchmarkS) TestBenchmarkMem(c *C) {
helper := FixtureHelper{sleep: 100000}
output := String{}
runConf := RunConf{
Output: &output,
Benchmark: true,
BenchmarkMem: true,
BenchmarkTime: 10000000,
Filter: "Benchmark3",
}
Run(&helper, &runConf)
expected := "PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Benchmark3\t\\s+ [0-9]+\t\\s+ *[0-9]+ ns/op\t\\s+ [0-9]+ B/op\t\\s+ [1-9]+ allocs/op\n"
c.Assert(output.value, Matches, expected)
}

View File

@ -1,82 +0,0 @@
// These initial tests are for bootstrapping. They verify that we can
// basically use the testing infrastructure itself to check if the test
// system is working.
//
// These tests use will break down the test runner badly in case of
// errors because if they simply fail, we can't be sure the developer
// will ever see anything (because failing means the failing system
// somehow isn't working! :-)
//
// Do not assume *any* internal functionality works as expected besides
// what's actually tested here.
package check_test
import (
"fmt"
"gitee.com/johng/gf/third/gopkg.in/check.v1"
"strings"
)
type BootstrapS struct{}
var boostrapS = check.Suite(&BootstrapS{})
func (s *BootstrapS) TestCountSuite(c *check.C) {
suitesRun += 1
}
func (s *BootstrapS) TestFailedAndFail(c *check.C) {
if c.Failed() {
critical("c.Failed() must be false first!")
}
c.Fail()
if !c.Failed() {
critical("c.Fail() didn't put the test in a failed state!")
}
c.Succeed()
}
func (s *BootstrapS) TestFailedAndSucceed(c *check.C) {
c.Fail()
c.Succeed()
if c.Failed() {
critical("c.Succeed() didn't put the test back in a non-failed state")
}
}
func (s *BootstrapS) TestLogAndGetTestLog(c *check.C) {
c.Log("Hello there!")
log := c.GetTestLog()
if log != "Hello there!\n" {
critical(fmt.Sprintf("Log() or GetTestLog() is not working! Got: %#v", log))
}
}
func (s *BootstrapS) TestLogfAndGetTestLog(c *check.C) {
c.Logf("Hello %v", "there!")
log := c.GetTestLog()
if log != "Hello there!\n" {
critical(fmt.Sprintf("Logf() or GetTestLog() is not working! Got: %#v", log))
}
}
func (s *BootstrapS) TestRunShowsErrors(c *check.C) {
output := String{}
check.Run(&FailHelper{}, &check.RunConf{Output: &output})
if strings.Index(output.value, "Expected failure!") == -1 {
critical(fmt.Sprintf("RunWithWriter() output did not contain the "+
"expected failure! Got: %#v",
output.value))
}
}
func (s *BootstrapS) TestRunDoesntShowSuccesses(c *check.C) {
output := String{}
check.Run(&SuccessHelper{}, &check.RunConf{Output: &output})
if strings.Index(output.value, "Expected success!") != -1 {
critical(fmt.Sprintf("RunWithWriter() output contained a successful "+
"test! Got: %#v",
output.value))
}
}

View File

@ -1,882 +0,0 @@
// Package check is a rich testing extension for Go's testing package.
//
// For details about the project, see:
//
// http://labix.org/gocheck
//
package check
import (
"bytes"
"errors"
"fmt"
"io"
"math/rand"
"os"
"path"
"path/filepath"
"reflect"
"regexp"
"runtime"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
)
// -----------------------------------------------------------------------
// Internal type which deals with suite method calling.
const (
fixtureKd = iota
testKd
)
type funcKind int
const (
succeededSt = iota
failedSt
skippedSt
panickedSt
fixturePanickedSt
missedSt
)
type funcStatus uint32
// A method value can't reach its own Method structure.
type methodType struct {
reflect.Value
Info reflect.Method
}
func newMethod(receiver reflect.Value, i int) *methodType {
return &methodType{receiver.Method(i), receiver.Type().Method(i)}
}
func (method *methodType) PC() uintptr {
return method.Info.Func.Pointer()
}
func (method *methodType) suiteName() string {
t := method.Info.Type.In(0)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
return t.Name()
}
func (method *methodType) String() string {
return method.suiteName() + "." + method.Info.Name
}
func (method *methodType) matches(re *regexp.Regexp) bool {
return (re.MatchString(method.Info.Name) ||
re.MatchString(method.suiteName()) ||
re.MatchString(method.String()))
}
type C struct {
method *methodType
kind funcKind
testName string
_status funcStatus
logb *logger
logw io.Writer
done chan *C
reason string
mustFail bool
tempDir *tempDir
benchMem bool
startTime time.Time
timer
}
func (c *C) status() funcStatus {
return funcStatus(atomic.LoadUint32((*uint32)(&c._status)))
}
func (c *C) setStatus(s funcStatus) {
atomic.StoreUint32((*uint32)(&c._status), uint32(s))
}
func (c *C) stopNow() {
runtime.Goexit()
}
// logger is a concurrency safe byte.Buffer
type logger struct {
sync.Mutex
writer bytes.Buffer
}
func (l *logger) Write(buf []byte) (int, error) {
l.Lock()
defer l.Unlock()
return l.writer.Write(buf)
}
func (l *logger) WriteTo(w io.Writer) (int64, error) {
l.Lock()
defer l.Unlock()
return l.writer.WriteTo(w)
}
func (l *logger) String() string {
l.Lock()
defer l.Unlock()
return l.writer.String()
}
// -----------------------------------------------------------------------
// Handling of temporary files and directories.
type tempDir struct {
sync.Mutex
path string
counter int
}
func (td *tempDir) newPath() string {
td.Lock()
defer td.Unlock()
if td.path == "" {
var err error
for i := 0; i != 100; i++ {
path := fmt.Sprintf("%s%ccheck-%d", os.TempDir(), os.PathSeparator, rand.Int())
if err = os.Mkdir(path, 0700); err == nil {
td.path = path
break
}
}
if td.path == "" {
panic("Couldn't create temporary directory: " + err.Error())
}
}
result := filepath.Join(td.path, strconv.Itoa(td.counter))
td.counter++
return result
}
func (td *tempDir) removeAll() {
td.Lock()
defer td.Unlock()
if td.path != "" {
err := os.RemoveAll(td.path)
if err != nil {
fmt.Fprintf(os.Stderr, "WARNING: Error cleaning up temporaries: "+err.Error())
}
}
}
// Create a new temporary directory which is automatically removed after
// the suite finishes running.
func (c *C) MkDir() string {
path := c.tempDir.newPath()
if err := os.Mkdir(path, 0700); err != nil {
panic(fmt.Sprintf("Couldn't create temporary directory %s: %s", path, err.Error()))
}
return path
}
// -----------------------------------------------------------------------
// Low-level logging functions.
func (c *C) log(args ...interface{}) {
c.writeLog([]byte(fmt.Sprint(args...) + "\n"))
}
func (c *C) logf(format string, args ...interface{}) {
c.writeLog([]byte(fmt.Sprintf(format+"\n", args...)))
}
func (c *C) logNewLine() {
c.writeLog([]byte{'\n'})
}
func (c *C) writeLog(buf []byte) {
c.logb.Write(buf)
if c.logw != nil {
c.logw.Write(buf)
}
}
func hasStringOrError(x interface{}) (ok bool) {
_, ok = x.(fmt.Stringer)
if ok {
return
}
_, ok = x.(error)
return
}
func (c *C) logValue(label string, value interface{}) {
if label == "" {
if hasStringOrError(value) {
c.logf("... %#v (%q)", value, value)
} else {
c.logf("... %#v", value)
}
} else if value == nil {
c.logf("... %s = nil", label)
} else {
if hasStringOrError(value) {
fv := fmt.Sprintf("%#v", value)
qv := fmt.Sprintf("%q", value)
if fv != qv {
c.logf("... %s %s = %s (%s)", label, reflect.TypeOf(value), fv, qv)
return
}
}
if s, ok := value.(string); ok && isMultiLine(s) {
c.logf(`... %s %s = "" +`, label, reflect.TypeOf(value))
c.logMultiLine(s)
} else {
c.logf("... %s %s = %#v", label, reflect.TypeOf(value), value)
}
}
}
func formatMultiLine(s string, quote bool) []byte {
b := make([]byte, 0, len(s)*2)
i := 0
n := len(s)
for i < n {
j := i + 1
for j < n && s[j-1] != '\n' {
j++
}
b = append(b, "... "...)
if quote {
b = strconv.AppendQuote(b, s[i:j])
} else {
b = append(b, s[i:j]...)
b = bytes.TrimSpace(b)
}
if quote && j < n {
b = append(b, " +"...)
}
b = append(b, '\n')
i = j
}
return b
}
func (c *C) logMultiLine(s string) {
c.writeLog(formatMultiLine(s, true))
}
func isMultiLine(s string) bool {
for i := 0; i+1 < len(s); i++ {
if s[i] == '\n' {
return true
}
}
return false
}
func (c *C) logString(issue string) {
c.log("... ", issue)
}
func (c *C) logCaller(skip int) {
// This is a bit heavier than it ought to be.
skip++ // Our own frame.
pc, callerFile, callerLine, ok := runtime.Caller(skip)
if !ok {
return
}
var testFile string
var testLine int
testFunc := runtime.FuncForPC(c.method.PC())
if runtime.FuncForPC(pc) != testFunc {
for {
skip++
if pc, file, line, ok := runtime.Caller(skip); ok {
// Note that the test line may be different on
// distinct calls for the same test. Showing
// the "internal" line is helpful when debugging.
if runtime.FuncForPC(pc) == testFunc {
testFile, testLine = file, line
break
}
} else {
break
}
}
}
if testFile != "" && (testFile != callerFile || testLine != callerLine) {
c.logCode(testFile, testLine)
}
c.logCode(callerFile, callerLine)
}
func (c *C) logCode(path string, line int) {
c.logf("%s:%d:", nicePath(path), line)
code, err := printLine(path, line)
if code == "" {
code = "..." // XXX Open the file and take the raw line.
if err != nil {
code += err.Error()
}
}
c.log(indent(code, " "))
}
var valueGo = filepath.Join("reflect", "value.go")
var asmGo = filepath.Join("runtime", "asm_")
func (c *C) logPanic(skip int, value interface{}) {
skip++ // Our own frame.
initialSkip := skip
for ; ; skip++ {
if pc, file, line, ok := runtime.Caller(skip); ok {
if skip == initialSkip {
c.logf("... Panic: %s (PC=0x%X)\n", value, pc)
}
name := niceFuncName(pc)
path := nicePath(file)
if strings.Contains(path, "/gopkg.in/check.v") {
continue
}
if name == "Value.call" && strings.HasSuffix(path, valueGo) {
continue
}
if (name == "call16" || name == "call32") && strings.Contains(path, asmGo) {
continue
}
c.logf("%s:%d\n in %s", nicePath(file), line, name)
} else {
break
}
}
}
func (c *C) logSoftPanic(issue string) {
c.log("... Panic: ", issue)
}
func (c *C) logArgPanic(method *methodType, expectedType string) {
c.logf("... Panic: %s argument should be %s",
niceFuncName(method.PC()), expectedType)
}
// -----------------------------------------------------------------------
// Some simple formatting helpers.
var initWD, initWDErr = os.Getwd()
func init() {
if initWDErr == nil {
initWD = strings.Replace(initWD, "\\", "/", -1) + "/"
}
}
func nicePath(path string) string {
if initWDErr == nil {
if strings.HasPrefix(path, initWD) {
return path[len(initWD):]
}
}
return path
}
func niceFuncPath(pc uintptr) string {
function := runtime.FuncForPC(pc)
if function != nil {
filename, line := function.FileLine(pc)
return fmt.Sprintf("%s:%d", nicePath(filename), line)
}
return "<unknown path>"
}
func niceFuncName(pc uintptr) string {
function := runtime.FuncForPC(pc)
if function != nil {
name := path.Base(function.Name())
if i := strings.Index(name, "."); i > 0 {
name = name[i+1:]
}
if strings.HasPrefix(name, "(*") {
if i := strings.Index(name, ")"); i > 0 {
name = name[2:i] + name[i+1:]
}
}
if i := strings.LastIndex(name, ".*"); i != -1 {
name = name[:i] + "." + name[i+2:]
}
if i := strings.LastIndex(name, "·"); i != -1 {
name = name[:i] + "." + name[i+2:]
}
return name
}
return "<unknown function>"
}
// -----------------------------------------------------------------------
// Result tracker to aggregate call results.
type Result struct {
Succeeded int
Failed int
Skipped int
Panicked int
FixturePanicked int
ExpectedFailures int
Missed int // Not even tried to run, related to a panic in the fixture.
RunError error // Houston, we've got a problem.
WorkDir string // If KeepWorkDir is true
}
type resultTracker struct {
result Result
_lastWasProblem bool
_waiting int
_missed int
_expectChan chan *C
_doneChan chan *C
_stopChan chan bool
}
func newResultTracker() *resultTracker {
return &resultTracker{_expectChan: make(chan *C), // Synchronous
_doneChan: make(chan *C, 32), // Asynchronous
_stopChan: make(chan bool)} // Synchronous
}
func (tracker *resultTracker) start() {
go tracker._loopRoutine()
}
func (tracker *resultTracker) waitAndStop() {
<-tracker._stopChan
}
func (tracker *resultTracker) expectCall(c *C) {
tracker._expectChan <- c
}
func (tracker *resultTracker) callDone(c *C) {
tracker._doneChan <- c
}
func (tracker *resultTracker) _loopRoutine() {
for {
var c *C
if tracker._waiting > 0 {
// Calls still running. Can't stop.
select {
// XXX Reindent this (not now to make diff clear)
case <-tracker._expectChan:
tracker._waiting++
case c = <-tracker._doneChan:
tracker._waiting--
switch c.status() {
case succeededSt:
if c.kind == testKd {
if c.mustFail {
tracker.result.ExpectedFailures++
} else {
tracker.result.Succeeded++
}
}
case failedSt:
tracker.result.Failed++
case panickedSt:
if c.kind == fixtureKd {
tracker.result.FixturePanicked++
} else {
tracker.result.Panicked++
}
case fixturePanickedSt:
// Track it as missed, since the panic
// was on the fixture, not on the test.
tracker.result.Missed++
case missedSt:
tracker.result.Missed++
case skippedSt:
if c.kind == testKd {
tracker.result.Skipped++
}
}
}
} else {
// No calls. Can stop, but no done calls here.
select {
case tracker._stopChan <- true:
return
case <-tracker._expectChan:
tracker._waiting++
case <-tracker._doneChan:
panic("Tracker got an unexpected done call.")
}
}
}
}
// -----------------------------------------------------------------------
// The underlying suite runner.
type suiteRunner struct {
suite interface{}
setUpSuite, tearDownSuite *methodType
setUpTest, tearDownTest *methodType
tests []*methodType
tracker *resultTracker
tempDir *tempDir
keepDir bool
output *outputWriter
reportedProblemLast bool
benchTime time.Duration
benchMem bool
}
type RunConf struct {
Output io.Writer
Stream bool
Verbose bool
Filter string
Benchmark bool
BenchmarkTime time.Duration // Defaults to 1 second
BenchmarkMem bool
KeepWorkDir bool
}
// Create a new suiteRunner able to run all methods in the given suite.
func newSuiteRunner(suite interface{}, runConf *RunConf) *suiteRunner {
var conf RunConf
if runConf != nil {
conf = *runConf
}
if conf.Output == nil {
conf.Output = os.Stdout
}
if conf.Benchmark {
conf.Verbose = true
}
suiteType := reflect.TypeOf(suite)
suiteNumMethods := suiteType.NumMethod()
suiteValue := reflect.ValueOf(suite)
runner := &suiteRunner{
suite: suite,
output: newOutputWriter(conf.Output, conf.Stream, conf.Verbose),
tracker: newResultTracker(),
benchTime: conf.BenchmarkTime,
benchMem: conf.BenchmarkMem,
tempDir: &tempDir{},
keepDir: conf.KeepWorkDir,
tests: make([]*methodType, 0, suiteNumMethods),
}
if runner.benchTime == 0 {
runner.benchTime = 1 * time.Second
}
var filterRegexp *regexp.Regexp
if conf.Filter != "" {
regexp, err := regexp.Compile(conf.Filter)
if err != nil {
msg := "Bad filter expression: " + err.Error()
runner.tracker.result.RunError = errors.New(msg)
return runner
}
filterRegexp = regexp
}
for i := 0; i != suiteNumMethods; i++ {
method := newMethod(suiteValue, i)
switch method.Info.Name {
case "SetUpSuite":
runner.setUpSuite = method
case "TearDownSuite":
runner.tearDownSuite = method
case "SetUpTest":
runner.setUpTest = method
case "TearDownTest":
runner.tearDownTest = method
default:
prefix := "Test"
if conf.Benchmark {
prefix = "Benchmark"
}
if !strings.HasPrefix(method.Info.Name, prefix) {
continue
}
if filterRegexp == nil || method.matches(filterRegexp) {
runner.tests = append(runner.tests, method)
}
}
}
return runner
}
// Run all methods in the given suite.
func (runner *suiteRunner) run() *Result {
if runner.tracker.result.RunError == nil && len(runner.tests) > 0 {
runner.tracker.start()
if runner.checkFixtureArgs() {
c := runner.runFixture(runner.setUpSuite, "", nil)
if c == nil || c.status() == succeededSt {
for i := 0; i != len(runner.tests); i++ {
c := runner.runTest(runner.tests[i])
if c.status() == fixturePanickedSt {
runner.skipTests(missedSt, runner.tests[i+1:])
break
}
}
} else if c != nil && c.status() == skippedSt {
runner.skipTests(skippedSt, runner.tests)
} else {
runner.skipTests(missedSt, runner.tests)
}
runner.runFixture(runner.tearDownSuite, "", nil)
} else {
runner.skipTests(missedSt, runner.tests)
}
runner.tracker.waitAndStop()
if runner.keepDir {
runner.tracker.result.WorkDir = runner.tempDir.path
} else {
runner.tempDir.removeAll()
}
}
return &runner.tracker.result
}
// Create a call object with the given suite method, and fork a
// goroutine with the provided dispatcher for running it.
func (runner *suiteRunner) forkCall(method *methodType, kind funcKind, testName string, logb *logger, dispatcher func(c *C)) *C {
var logw io.Writer
if runner.output.Stream {
logw = runner.output
}
if logb == nil {
logb = new(logger)
}
c := &C{
method: method,
kind: kind,
testName: testName,
logb: logb,
logw: logw,
tempDir: runner.tempDir,
done: make(chan *C, 1),
timer: timer{benchTime: runner.benchTime},
startTime: time.Now(),
benchMem: runner.benchMem,
}
runner.tracker.expectCall(c)
go (func() {
runner.reportCallStarted(c)
defer runner.callDone(c)
dispatcher(c)
})()
return c
}
// Same as forkCall(), but wait for call to finish before returning.
func (runner *suiteRunner) runFunc(method *methodType, kind funcKind, testName string, logb *logger, dispatcher func(c *C)) *C {
c := runner.forkCall(method, kind, testName, logb, dispatcher)
<-c.done
return c
}
// Handle a finished call. If there were any panics, update the call status
// accordingly. Then, mark the call as done and report to the tracker.
func (runner *suiteRunner) callDone(c *C) {
value := recover()
if value != nil {
switch v := value.(type) {
case *fixturePanic:
if v.status == skippedSt {
c.setStatus(skippedSt)
} else {
c.logSoftPanic("Fixture has panicked (see related PANIC)")
c.setStatus(fixturePanickedSt)
}
default:
c.logPanic(1, value)
c.setStatus(panickedSt)
}
}
if c.mustFail {
switch c.status() {
case failedSt:
c.setStatus(succeededSt)
case succeededSt:
c.setStatus(failedSt)
c.logString("Error: Test succeeded, but was expected to fail")
c.logString("Reason: " + c.reason)
}
}
runner.reportCallDone(c)
c.done <- c
}
// Runs a fixture call synchronously. The fixture will still be run in a
// goroutine like all suite methods, but this method will not return
// while the fixture goroutine is not done, because the fixture must be
// run in a desired order.
func (runner *suiteRunner) runFixture(method *methodType, testName string, logb *logger) *C {
if method != nil {
c := runner.runFunc(method, fixtureKd, testName, logb, func(c *C) {
c.ResetTimer()
c.StartTimer()
defer c.StopTimer()
c.method.Call([]reflect.Value{reflect.ValueOf(c)})
})
return c
}
return nil
}
// Run the fixture method with runFixture(), but panic with a fixturePanic{}
// in case the fixture method panics. This makes it easier to track the
// fixture panic together with other call panics within forkTest().
func (runner *suiteRunner) runFixtureWithPanic(method *methodType, testName string, logb *logger, skipped *bool) *C {
if skipped != nil && *skipped {
return nil
}
c := runner.runFixture(method, testName, logb)
if c != nil && c.status() != succeededSt {
if skipped != nil {
*skipped = c.status() == skippedSt
}
panic(&fixturePanic{c.status(), method})
}
return c
}
type fixturePanic struct {
status funcStatus
method *methodType
}
// Run the suite test method, together with the test-specific fixture,
// asynchronously.
func (runner *suiteRunner) forkTest(method *methodType) *C {
testName := method.String()
return runner.forkCall(method, testKd, testName, nil, func(c *C) {
var skipped bool
defer runner.runFixtureWithPanic(runner.tearDownTest, testName, nil, &skipped)
defer c.StopTimer()
benchN := 1
for {
runner.runFixtureWithPanic(runner.setUpTest, testName, c.logb, &skipped)
mt := c.method.Type()
if mt.NumIn() != 1 || mt.In(0) != reflect.TypeOf(c) {
// Rather than a plain panic, provide a more helpful message when
// the argument type is incorrect.
c.setStatus(panickedSt)
c.logArgPanic(c.method, "*check.C")
return
}
if strings.HasPrefix(c.method.Info.Name, "Test") {
c.ResetTimer()
c.StartTimer()
c.method.Call([]reflect.Value{reflect.ValueOf(c)})
return
}
if !strings.HasPrefix(c.method.Info.Name, "Benchmark") {
panic("unexpected method prefix: " + c.method.Info.Name)
}
runtime.GC()
c.N = benchN
c.ResetTimer()
c.StartTimer()
c.method.Call([]reflect.Value{reflect.ValueOf(c)})
c.StopTimer()
if c.status() != succeededSt || c.duration >= c.benchTime || benchN >= 1e9 {
return
}
perOpN := int(1e9)
if c.nsPerOp() != 0 {
perOpN = int(c.benchTime.Nanoseconds() / c.nsPerOp())
}
// Logic taken from the stock testing package:
// - Run more iterations than we think we'll need for a second (1.5x).
// - Don't grow too fast in case we had timing errors previously.
// - Be sure to run at least one more than last time.
benchN = max(min(perOpN+perOpN/2, 100*benchN), benchN+1)
benchN = roundUp(benchN)
skipped = true // Don't run the deferred one if this panics.
runner.runFixtureWithPanic(runner.tearDownTest, testName, nil, nil)
skipped = false
}
})
}
// Same as forkTest(), but wait for the test to finish before returning.
func (runner *suiteRunner) runTest(method *methodType) *C {
c := runner.forkTest(method)
<-c.done
return c
}
// Helper to mark tests as skipped or missed. A bit heavy for what
// it does, but it enables homogeneous handling of tracking, including
// nice verbose output.
func (runner *suiteRunner) skipTests(status funcStatus, methods []*methodType) {
for _, method := range methods {
runner.runFunc(method, testKd, "", nil, func(c *C) {
c.setStatus(status)
})
}
}
// Verify if the fixture arguments are *check.C. In case of errors,
// log the error as a panic in the fixture method call, and return false.
func (runner *suiteRunner) checkFixtureArgs() bool {
succeeded := true
argType := reflect.TypeOf(&C{})
for _, method := range []*methodType{runner.setUpSuite, runner.tearDownSuite, runner.setUpTest, runner.tearDownTest} {
if method != nil {
mt := method.Type()
if mt.NumIn() != 1 || mt.In(0) != argType {
succeeded = false
runner.runFunc(method, fixtureKd, "", nil, func(c *C) {
c.logArgPanic(method, "*check.C")
c.setStatus(panickedSt)
})
}
}
}
return succeeded
}
func (runner *suiteRunner) reportCallStarted(c *C) {
runner.output.WriteCallStarted("START", c)
}
func (runner *suiteRunner) reportCallDone(c *C) {
runner.tracker.callDone(c)
switch c.status() {
case succeededSt:
if c.mustFail {
runner.output.WriteCallSuccess("FAIL EXPECTED", c)
} else {
runner.output.WriteCallSuccess("PASS", c)
}
case skippedSt:
runner.output.WriteCallSuccess("SKIP", c)
case failedSt:
runner.output.WriteCallProblem("FAIL", c)
case panickedSt:
runner.output.WriteCallProblem("PANIC", c)
case fixturePanickedSt:
// That's a testKd call reporting that its fixture
// has panicked. The fixture call which caused the
// panic itself was tracked above. We'll report to
// aid debugging.
runner.output.WriteCallProblem("PANIC", c)
case missedSt:
runner.output.WriteCallSuccess("MISS", c)
}
}

View File

@ -1,207 +0,0 @@
// This file contains just a few generic helpers which are used by the
// other test files.
package check_test
import (
"flag"
"fmt"
"os"
"regexp"
"runtime"
"testing"
"time"
"gitee.com/johng/gf/third/gopkg.in/check.v1"
)
// We count the number of suites run at least to get a vague hint that the
// test suite is behaving as it should. Otherwise a bug introduced at the
// very core of the system could go unperceived.
const suitesRunExpected = 8
var suitesRun int = 0
func Test(t *testing.T) {
check.TestingT(t)
if suitesRun != suitesRunExpected && flag.Lookup("check.f").Value.String() == "" {
critical(fmt.Sprintf("Expected %d suites to run rather than %d",
suitesRunExpected, suitesRun))
}
}
// -----------------------------------------------------------------------
// Helper functions.
// Break down badly. This is used in test cases which can't yet assume
// that the fundamental bits are working.
func critical(error string) {
fmt.Fprintln(os.Stderr, "CRITICAL: "+error)
os.Exit(1)
}
// Return the file line where it's called.
func getMyLine() int {
if _, _, line, ok := runtime.Caller(1); ok {
return line
}
return -1
}
// -----------------------------------------------------------------------
// Helper type implementing a basic io.Writer for testing output.
// Type implementing the io.Writer interface for analyzing output.
type String struct {
value string
}
// The only function required by the io.Writer interface. Will append
// written data to the String.value string.
func (s *String) Write(p []byte) (n int, err error) {
s.value += string(p)
return len(p), nil
}
// Trivial wrapper to test errors happening on a different file
// than the test itself.
func checkEqualWrapper(c *check.C, obtained, expected interface{}) (result bool, line int) {
return c.Check(obtained, check.Equals, expected), getMyLine()
}
// -----------------------------------------------------------------------
// Helper suite for testing basic fail behavior.
type FailHelper struct {
testLine int
}
func (s *FailHelper) TestLogAndFail(c *check.C) {
s.testLine = getMyLine() - 1
c.Log("Expected failure!")
c.Fail()
}
// -----------------------------------------------------------------------
// Helper suite for testing basic success behavior.
type SuccessHelper struct{}
func (s *SuccessHelper) TestLogAndSucceed(c *check.C) {
c.Log("Expected success!")
}
// -----------------------------------------------------------------------
// Helper suite for testing ordering and behavior of fixture.
type FixtureHelper struct {
calls []string
panicOn string
skip bool
skipOnN int
sleepOn string
sleep time.Duration
bytes int64
}
func (s *FixtureHelper) trace(name string, c *check.C) {
s.calls = append(s.calls, name)
if name == s.panicOn {
panic(name)
}
if s.sleep > 0 && s.sleepOn == name {
time.Sleep(s.sleep)
}
if s.skip && s.skipOnN == len(s.calls)-1 {
c.Skip("skipOnN == n")
}
}
func (s *FixtureHelper) SetUpSuite(c *check.C) {
s.trace("SetUpSuite", c)
}
func (s *FixtureHelper) TearDownSuite(c *check.C) {
s.trace("TearDownSuite", c)
}
func (s *FixtureHelper) SetUpTest(c *check.C) {
s.trace("SetUpTest", c)
}
func (s *FixtureHelper) TearDownTest(c *check.C) {
s.trace("TearDownTest", c)
}
func (s *FixtureHelper) Test1(c *check.C) {
s.trace("Test1", c)
}
func (s *FixtureHelper) Test2(c *check.C) {
s.trace("Test2", c)
}
func (s *FixtureHelper) Benchmark1(c *check.C) {
s.trace("Benchmark1", c)
for i := 0; i < c.N; i++ {
time.Sleep(s.sleep)
}
}
func (s *FixtureHelper) Benchmark2(c *check.C) {
s.trace("Benchmark2", c)
c.SetBytes(1024)
for i := 0; i < c.N; i++ {
time.Sleep(s.sleep)
}
}
func (s *FixtureHelper) Benchmark3(c *check.C) {
var x []int64
s.trace("Benchmark3", c)
for i := 0; i < c.N; i++ {
time.Sleep(s.sleep)
x = make([]int64, 5)
_ = x
}
}
// -----------------------------------------------------------------------
// Helper which checks the state of the test and ensures that it matches
// the given expectations. Depends on c.Errorf() working, so shouldn't
// be used to test this one function.
type expectedState struct {
name string
result interface{}
failed bool
log string
}
// Verify the state of the test. Note that since this also verifies if
// the test is supposed to be in a failed state, no other checks should
// be done in addition to what is being tested.
func checkState(c *check.C, result interface{}, expected *expectedState) {
failed := c.Failed()
c.Succeed()
log := c.GetTestLog()
matched, matchError := regexp.MatchString("^"+expected.log+"$", log)
if matchError != nil {
c.Errorf("Error in matching expression used in testing %s: %v",
expected.name, matchError)
} else if !matched {
c.Errorf("%s logged:\n----------\n%s----------\n\nExpected:\n----------\n%s\n----------",
expected.name, log, expected.log)
}
if result != expected.result {
c.Errorf("%s returned %#v rather than %#v",
expected.name, result, expected.result)
}
if failed != expected.failed {
if failed {
c.Errorf("%s has failed when it shouldn't", expected.name)
} else {
c.Errorf("%s has not failed when it should", expected.name)
}
}
}

View File

@ -1,524 +0,0 @@
package check
import (
"fmt"
"reflect"
"regexp"
"strings"
"gitee.com/johng/gf/third/github.com/kr/pretty"
)
// -----------------------------------------------------------------------
// CommentInterface and Commentf helper, to attach extra information to checks.
type comment struct {
format string
args []interface{}
}
// Commentf returns an infomational value to use with Assert or Check calls.
// If the checker test fails, the provided arguments will be passed to
// fmt.Sprintf, and will be presented next to the logged failure.
//
// For example:
//
// c.Assert(v, Equals, 42, Commentf("Iteration #%d failed.", i))
//
// Note that if the comment is constant, a better option is to
// simply use a normal comment right above or next to the line, as
// it will also get printed with any errors:
//
// c.Assert(l, Equals, 8192) // Ensure buffer size is correct (bug #123)
//
func Commentf(format string, args ...interface{}) CommentInterface {
return &comment{format, args}
}
// CommentInterface must be implemented by types that attach extra
// information to failed checks. See the Commentf function for details.
type CommentInterface interface {
CheckCommentString() string
}
func (c *comment) CheckCommentString() string {
return fmt.Sprintf(c.format, c.args...)
}
// -----------------------------------------------------------------------
// The Checker interface.
// The Checker interface must be provided by checkers used with
// the Assert and Check verification methods.
type Checker interface {
Info() *CheckerInfo
Check(params []interface{}, names []string) (result bool, error string)
}
// See the Checker interface.
type CheckerInfo struct {
Name string
Params []string
}
func (info *CheckerInfo) Info() *CheckerInfo {
return info
}
// -----------------------------------------------------------------------
// Not checker logic inverter.
// The Not checker inverts the logic of the provided checker. The
// resulting checker will succeed where the original one failed, and
// vice-versa.
//
// For example:
//
// c.Assert(a, Not(Equals), b)
//
func Not(checker Checker) Checker {
return &notChecker{checker}
}
type notChecker struct {
sub Checker
}
func (checker *notChecker) Info() *CheckerInfo {
info := *checker.sub.Info()
info.Name = "Not(" + info.Name + ")"
return &info
}
func (checker *notChecker) Check(params []interface{}, names []string) (result bool, error string) {
result, error = checker.sub.Check(params, names)
result = !result
if result {
// clear error message if the new result is true
error = ""
}
return
}
// -----------------------------------------------------------------------
// IsNil checker.
type isNilChecker struct {
*CheckerInfo
}
// The IsNil checker tests whether the obtained value is nil.
//
// For example:
//
// c.Assert(err, IsNil)
//
var IsNil Checker = &isNilChecker{
&CheckerInfo{Name: "IsNil", Params: []string{"value"}},
}
func (checker *isNilChecker) Check(params []interface{}, names []string) (result bool, error string) {
return isNil(params[0]), ""
}
func isNil(obtained interface{}) (result bool) {
if obtained == nil {
result = true
} else {
switch v := reflect.ValueOf(obtained); v.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
return v.IsNil()
}
}
return
}
// -----------------------------------------------------------------------
// NotNil checker. Alias for Not(IsNil), since it's so common.
type notNilChecker struct {
*CheckerInfo
}
// The NotNil checker verifies that the obtained value is not nil.
//
// For example:
//
// c.Assert(iface, NotNil)
//
// This is an alias for Not(IsNil), made available since it's a
// fairly common check.
//
var NotNil Checker = &notNilChecker{
&CheckerInfo{Name: "NotNil", Params: []string{"value"}},
}
func (checker *notNilChecker) Check(params []interface{}, names []string) (result bool, error string) {
return !isNil(params[0]), ""
}
// -----------------------------------------------------------------------
// Equals checker.
func diffworthy(a interface{}) bool {
t := reflect.TypeOf(a)
switch t.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.Struct, reflect.String, reflect.Ptr:
return true
}
return false
}
// formatUnequal will dump the actual and expected values into a textual
// representation and return an error message containing a diff.
func formatUnequal(obtained interface{}, expected interface{}) string {
// We do not do diffs for basic types because go-check already
// shows them very cleanly.
if !diffworthy(obtained) || !diffworthy(expected) {
return ""
}
// Handle strings, short strings are ignored (go-check formats
// them very nicely already). We do multi-line strings by
// generating two string slices and using kr.Diff to compare
// those (kr.Diff does not do string diffs by itself).
aStr, aOK := obtained.(string)
bStr, bOK := expected.(string)
if aOK && bOK {
l1 := strings.Split(aStr, "\n")
l2 := strings.Split(bStr, "\n")
// the "2" here is a bit arbitrary
if len(l1) > 2 && len(l2) > 2 {
diff := pretty.Diff(l1, l2)
return fmt.Sprintf(`String difference:
%s`, formatMultiLine(strings.Join(diff, "\n"), false))
}
// string too short
return ""
}
// generic diff
diff := pretty.Diff(obtained, expected)
if len(diff) == 0 {
// No diff, this happens when e.g. just struct
// pointers are different but the structs have
// identical values.
return ""
}
return fmt.Sprintf(`Difference:
%s`, formatMultiLine(strings.Join(diff, "\n"), false))
}
type equalsChecker struct {
*CheckerInfo
}
// The Equals checker verifies that the obtained value is equal to
// the expected value, according to usual Go semantics for ==.
//
// For example:
//
// c.Assert(value, Equals, 42)
//
var Equals Checker = &equalsChecker{
&CheckerInfo{Name: "Equals", Params: []string{"obtained", "expected"}},
}
func (checker *equalsChecker) Check(params []interface{}, names []string) (result bool, error string) {
defer func() {
if v := recover(); v != nil {
result = false
error = fmt.Sprint(v)
}
}()
result = params[0] == params[1]
if !result {
error = formatUnequal(params[0], params[1])
}
return
}
// -----------------------------------------------------------------------
// DeepEquals checker.
type deepEqualsChecker struct {
*CheckerInfo
}
// The DeepEquals checker verifies that the obtained value is deep-equal to
// the expected value. The check will work correctly even when facing
// slices, interfaces, and values of different types (which always fail
// the test).
//
// For example:
//
// c.Assert(value, DeepEquals, 42)
// c.Assert(array, DeepEquals, []string{"hi", "there"})
//
var DeepEquals Checker = &deepEqualsChecker{
&CheckerInfo{Name: "DeepEquals", Params: []string{"obtained", "expected"}},
}
func (checker *deepEqualsChecker) Check(params []interface{}, names []string) (result bool, error string) {
result = reflect.DeepEqual(params[0], params[1])
if !result {
error = formatUnequal(params[0], params[1])
}
return
}
// -----------------------------------------------------------------------
// HasLen checker.
type hasLenChecker struct {
*CheckerInfo
}
// The HasLen checker verifies that the obtained value has the
// provided length. In many cases this is superior to using Equals
// in conjunction with the len function because in case the check
// fails the value itself will be printed, instead of its length,
// providing more details for figuring the problem.
//
// For example:
//
// c.Assert(list, HasLen, 5)
//
var HasLen Checker = &hasLenChecker{
&CheckerInfo{Name: "HasLen", Params: []string{"obtained", "n"}},
}
func (checker *hasLenChecker) Check(params []interface{}, names []string) (result bool, error string) {
n, ok := params[1].(int)
if !ok {
return false, "n must be an int"
}
value := reflect.ValueOf(params[0])
switch value.Kind() {
case reflect.Map, reflect.Array, reflect.Slice, reflect.Chan, reflect.String:
default:
return false, "obtained value type has no length"
}
return value.Len() == n, ""
}
// -----------------------------------------------------------------------
// ErrorMatches checker.
type errorMatchesChecker struct {
*CheckerInfo
}
// The ErrorMatches checker verifies that the error value
// is non nil and matches the regular expression provided.
//
// For example:
//
// c.Assert(err, ErrorMatches, "perm.*denied")
//
var ErrorMatches Checker = errorMatchesChecker{
&CheckerInfo{Name: "ErrorMatches", Params: []string{"value", "regex"}},
}
func (checker errorMatchesChecker) Check(params []interface{}, names []string) (result bool, errStr string) {
if params[0] == nil {
return false, "Error value is nil"
}
err, ok := params[0].(error)
if !ok {
return false, "Value is not an error"
}
params[0] = err.Error()
names[0] = "error"
return matches(params[0], params[1])
}
// -----------------------------------------------------------------------
// Matches checker.
type matchesChecker struct {
*CheckerInfo
}
// The Matches checker verifies that the string provided as the obtained
// value (or the string resulting from obtained.String()) matches the
// regular expression provided.
//
// For example:
//
// c.Assert(err, Matches, "perm.*denied")
//
var Matches Checker = &matchesChecker{
&CheckerInfo{Name: "Matches", Params: []string{"value", "regex"}},
}
func (checker *matchesChecker) Check(params []interface{}, names []string) (result bool, error string) {
return matches(params[0], params[1])
}
func matches(value, regex interface{}) (result bool, error string) {
reStr, ok := regex.(string)
if !ok {
return false, "Regex must be a string"
}
valueStr, valueIsStr := value.(string)
if !valueIsStr {
if valueWithStr, valueHasStr := value.(fmt.Stringer); valueHasStr {
valueStr, valueIsStr = valueWithStr.String(), true
}
}
if valueIsStr {
matches, err := regexp.MatchString("^"+reStr+"$", valueStr)
if err != nil {
return false, "Can't compile regex: " + err.Error()
}
return matches, ""
}
return false, "Obtained value is not a string and has no .String()"
}
// -----------------------------------------------------------------------
// Panics checker.
type panicsChecker struct {
*CheckerInfo
}
// The Panics checker verifies that calling the provided zero-argument
// function will cause a panic which is deep-equal to the provided value.
//
// For example:
//
// c.Assert(func() { f(1, 2) }, Panics, &SomeErrorType{"BOOM"}).
//
//
var Panics Checker = &panicsChecker{
&CheckerInfo{Name: "Panics", Params: []string{"function", "expected"}},
}
func (checker *panicsChecker) Check(params []interface{}, names []string) (result bool, error string) {
f := reflect.ValueOf(params[0])
if f.Kind() != reflect.Func || f.Type().NumIn() != 0 {
return false, "Function must take zero arguments"
}
defer func() {
// If the function has not panicked, then don't do the check.
if error != "" {
return
}
params[0] = recover()
names[0] = "panic"
result = reflect.DeepEqual(params[0], params[1])
}()
f.Call(nil)
return false, "Function has not panicked"
}
type panicMatchesChecker struct {
*CheckerInfo
}
// The PanicMatches checker verifies that calling the provided zero-argument
// function will cause a panic with an error value matching
// the regular expression provided.
//
// For example:
//
// c.Assert(func() { f(1, 2) }, PanicMatches, `open.*: no such file or directory`).
//
//
var PanicMatches Checker = &panicMatchesChecker{
&CheckerInfo{Name: "PanicMatches", Params: []string{"function", "expected"}},
}
func (checker *panicMatchesChecker) Check(params []interface{}, names []string) (result bool, errmsg string) {
f := reflect.ValueOf(params[0])
if f.Kind() != reflect.Func || f.Type().NumIn() != 0 {
return false, "Function must take zero arguments"
}
defer func() {
// If the function has not panicked, then don't do the check.
if errmsg != "" {
return
}
obtained := recover()
names[0] = "panic"
if e, ok := obtained.(error); ok {
params[0] = e.Error()
} else if _, ok := obtained.(string); ok {
params[0] = obtained
} else {
errmsg = "Panic value is not a string or an error"
return
}
result, errmsg = matches(params[0], params[1])
}()
f.Call(nil)
return false, "Function has not panicked"
}
// -----------------------------------------------------------------------
// FitsTypeOf checker.
type fitsTypeChecker struct {
*CheckerInfo
}
// The FitsTypeOf checker verifies that the obtained value is
// assignable to a variable with the same type as the provided
// sample value.
//
// For example:
//
// c.Assert(value, FitsTypeOf, int64(0))
// c.Assert(value, FitsTypeOf, os.Error(nil))
//
var FitsTypeOf Checker = &fitsTypeChecker{
&CheckerInfo{Name: "FitsTypeOf", Params: []string{"obtained", "sample"}},
}
func (checker *fitsTypeChecker) Check(params []interface{}, names []string) (result bool, error string) {
obtained := reflect.ValueOf(params[0])
sample := reflect.ValueOf(params[1])
if !obtained.IsValid() {
return false, ""
}
if !sample.IsValid() {
return false, "Invalid sample value"
}
return obtained.Type().AssignableTo(sample.Type()), ""
}
// -----------------------------------------------------------------------
// Implements checker.
type implementsChecker struct {
*CheckerInfo
}
// The Implements checker verifies that the obtained value
// implements the interface specified via a pointer to an interface
// variable.
//
// For example:
//
// var e os.Error
// c.Assert(err, Implements, &e)
//
var Implements Checker = &implementsChecker{
&CheckerInfo{Name: "Implements", Params: []string{"obtained", "ifaceptr"}},
}
func (checker *implementsChecker) Check(params []interface{}, names []string) (result bool, error string) {
obtained := reflect.ValueOf(params[0])
ifaceptr := reflect.ValueOf(params[1])
if !obtained.IsValid() {
return false, ""
}
if !ifaceptr.IsValid() || ifaceptr.Kind() != reflect.Ptr || ifaceptr.Elem().Kind() != reflect.Interface {
return false, "ifaceptr should be a pointer to an interface variable"
}
return obtained.Type().Implements(ifaceptr.Elem().Type()), ""
}

View File

@ -1,287 +0,0 @@
package check_test
import (
"errors"
"reflect"
"runtime"
"gitee.com/johng/gf/third/gopkg.in/check.v1"
)
type CheckersS struct{}
var _ = check.Suite(&CheckersS{})
func testInfo(c *check.C, checker check.Checker, name string, paramNames []string) {
info := checker.Info()
if info.Name != name {
c.Fatalf("Got name %s, expected %s", info.Name, name)
}
if !reflect.DeepEqual(info.Params, paramNames) {
c.Fatalf("Got param names %#v, expected %#v", info.Params, paramNames)
}
}
func testCheck(c *check.C, checker check.Checker, result bool, error string, params ...interface{}) ([]interface{}, []string) {
info := checker.Info()
if len(params) != len(info.Params) {
c.Fatalf("unexpected param count in test; expected %d got %d", len(info.Params), len(params))
}
names := append([]string{}, info.Params...)
result_, error_ := checker.Check(params, names)
if result_ != result || error_ != error {
c.Fatalf("%s.Check(%#v) returned (%#v, %#v) rather than (%#v, %#v)",
info.Name, params, result_, error_, result, error)
}
return params, names
}
func (s *CheckersS) TestComment(c *check.C) {
bug := check.Commentf("a %d bc", 42)
comment := bug.CheckCommentString()
if comment != "a 42 bc" {
c.Fatalf("Commentf returned %#v", comment)
}
}
func (s *CheckersS) TestIsNil(c *check.C) {
testInfo(c, check.IsNil, "IsNil", []string{"value"})
testCheck(c, check.IsNil, true, "", nil)
testCheck(c, check.IsNil, false, "", "a")
testCheck(c, check.IsNil, true, "", (chan int)(nil))
testCheck(c, check.IsNil, false, "", make(chan int))
testCheck(c, check.IsNil, true, "", (error)(nil))
testCheck(c, check.IsNil, false, "", errors.New(""))
testCheck(c, check.IsNil, true, "", ([]int)(nil))
testCheck(c, check.IsNil, false, "", make([]int, 1))
testCheck(c, check.IsNil, false, "", int(0))
}
func (s *CheckersS) TestNotNil(c *check.C) {
testInfo(c, check.NotNil, "NotNil", []string{"value"})
testCheck(c, check.NotNil, false, "", nil)
testCheck(c, check.NotNil, true, "", "a")
testCheck(c, check.NotNil, false, "", (chan int)(nil))
testCheck(c, check.NotNil, true, "", make(chan int))
testCheck(c, check.NotNil, false, "", (error)(nil))
testCheck(c, check.NotNil, true, "", errors.New(""))
testCheck(c, check.NotNil, false, "", ([]int)(nil))
testCheck(c, check.NotNil, true, "", make([]int, 1))
}
func (s *CheckersS) TestNot(c *check.C) {
testInfo(c, check.Not(check.IsNil), "Not(IsNil)", []string{"value"})
testCheck(c, check.Not(check.IsNil), false, "", nil)
testCheck(c, check.Not(check.IsNil), true, "", "a")
testCheck(c, check.Not(check.Equals), true, "", 42, 43)
}
type simpleStruct struct {
i int
}
func (s *CheckersS) TestEquals(c *check.C) {
testInfo(c, check.Equals, "Equals", []string{"obtained", "expected"})
// The simplest.
testCheck(c, check.Equals, true, "", 42, 42)
testCheck(c, check.Equals, false, "", 42, 43)
// Different native types.
testCheck(c, check.Equals, false, "", int32(42), int64(42))
// With nil.
testCheck(c, check.Equals, false, "", 42, nil)
// Slices
testCheck(c, check.Equals, false, "runtime error: comparing uncomparable type []uint8", []byte{1, 2}, []byte{1, 2})
// Struct values
testCheck(c, check.Equals, true, "", simpleStruct{1}, simpleStruct{1})
testCheck(c, check.Equals, false, `Difference:
... i: 1 != 2
`, simpleStruct{1}, simpleStruct{2})
// Struct pointers, no difference in values, just pointer
testCheck(c, check.Equals, false, "", &simpleStruct{1}, &simpleStruct{1})
// Struct pointers, different pointers and different values
testCheck(c, check.Equals, false, `Difference:
... i: 1 != 2
`, &simpleStruct{1}, &simpleStruct{2})
}
func (s *CheckersS) TestDeepEquals(c *check.C) {
testInfo(c, check.DeepEquals, "DeepEquals", []string{"obtained", "expected"})
// The simplest.
testCheck(c, check.DeepEquals, true, "", 42, 42)
testCheck(c, check.DeepEquals, false, "", 42, 43)
// Different native types.
testCheck(c, check.DeepEquals, false, "", int32(42), int64(42))
// With nil.
testCheck(c, check.DeepEquals, false, "", 42, nil)
// Slices
testCheck(c, check.DeepEquals, true, "", []byte{1, 2}, []byte{1, 2})
testCheck(c, check.DeepEquals, false, `Difference:
... [1]: 2 != 3
`, []byte{1, 2}, []byte{1, 3})
// Struct values
testCheck(c, check.DeepEquals, true, "", simpleStruct{1}, simpleStruct{1})
testCheck(c, check.DeepEquals, false, `Difference:
... i: 1 != 2
`, simpleStruct{1}, simpleStruct{2})
// Struct pointers
testCheck(c, check.DeepEquals, true, "", &simpleStruct{1}, &simpleStruct{1})
s1 := &simpleStruct{1}
s2 := &simpleStruct{2}
testCheck(c, check.DeepEquals, false, `Difference:
... i: 1 != 2
`, s1, s2)
}
func (s *CheckersS) TestHasLen(c *check.C) {
testInfo(c, check.HasLen, "HasLen", []string{"obtained", "n"})
testCheck(c, check.HasLen, true, "", "abcd", 4)
testCheck(c, check.HasLen, true, "", []int{1, 2}, 2)
testCheck(c, check.HasLen, false, "", []int{1, 2}, 3)
testCheck(c, check.HasLen, false, "n must be an int", []int{1, 2}, "2")
testCheck(c, check.HasLen, false, "obtained value type has no length", nil, 2)
}
func (s *CheckersS) TestErrorMatches(c *check.C) {
testInfo(c, check.ErrorMatches, "ErrorMatches", []string{"value", "regex"})
testCheck(c, check.ErrorMatches, false, "Error value is nil", nil, "some error")
testCheck(c, check.ErrorMatches, false, "Value is not an error", 1, "some error")
testCheck(c, check.ErrorMatches, true, "", errors.New("some error"), "some error")
testCheck(c, check.ErrorMatches, true, "", errors.New("some error"), "so.*or")
// Verify params mutation
params, names := testCheck(c, check.ErrorMatches, false, "", errors.New("some error"), "other error")
c.Assert(params[0], check.Equals, "some error")
c.Assert(names[0], check.Equals, "error")
}
func (s *CheckersS) TestMatches(c *check.C) {
testInfo(c, check.Matches, "Matches", []string{"value", "regex"})
// Simple matching
testCheck(c, check.Matches, true, "", "abc", "abc")
testCheck(c, check.Matches, true, "", "abc", "a.c")
// Must match fully
testCheck(c, check.Matches, false, "", "abc", "ab")
testCheck(c, check.Matches, false, "", "abc", "bc")
// String()-enabled values accepted
testCheck(c, check.Matches, true, "", reflect.ValueOf("abc"), "a.c")
testCheck(c, check.Matches, false, "", reflect.ValueOf("abc"), "a.d")
// Some error conditions.
testCheck(c, check.Matches, false, "Obtained value is not a string and has no .String()", 1, "a.c")
testCheck(c, check.Matches, false, "Can't compile regex: error parsing regexp: missing closing ]: `[c$`", "abc", "a[c")
}
func (s *CheckersS) TestPanics(c *check.C) {
testInfo(c, check.Panics, "Panics", []string{"function", "expected"})
// Some errors.
testCheck(c, check.Panics, false, "Function has not panicked", func() bool { return false }, "BOOM")
testCheck(c, check.Panics, false, "Function must take zero arguments", 1, "BOOM")
// Plain strings.
testCheck(c, check.Panics, true, "", func() { panic("BOOM") }, "BOOM")
testCheck(c, check.Panics, false, "", func() { panic("KABOOM") }, "BOOM")
testCheck(c, check.Panics, true, "", func() bool { panic("BOOM") }, "BOOM")
// Error values.
testCheck(c, check.Panics, true, "", func() { panic(errors.New("BOOM")) }, errors.New("BOOM"))
testCheck(c, check.Panics, false, "", func() { panic(errors.New("KABOOM")) }, errors.New("BOOM"))
type deep struct{ i int }
// Deep value
testCheck(c, check.Panics, true, "", func() { panic(&deep{99}) }, &deep{99})
// Verify params/names mutation
params, names := testCheck(c, check.Panics, false, "", func() { panic(errors.New("KABOOM")) }, errors.New("BOOM"))
c.Assert(params[0], check.ErrorMatches, "KABOOM")
c.Assert(names[0], check.Equals, "panic")
// Verify a nil panic
testCheck(c, check.Panics, true, "", func() { panic(nil) }, nil)
testCheck(c, check.Panics, false, "", func() { panic(nil) }, "NOPE")
}
func (s *CheckersS) TestPanicMatches(c *check.C) {
testInfo(c, check.PanicMatches, "PanicMatches", []string{"function", "expected"})
// Error matching.
testCheck(c, check.PanicMatches, true, "", func() { panic(errors.New("BOOM")) }, "BO.M")
testCheck(c, check.PanicMatches, false, "", func() { panic(errors.New("KABOOM")) }, "BO.M")
// Some errors.
testCheck(c, check.PanicMatches, false, "Function has not panicked", func() bool { return false }, "BOOM")
testCheck(c, check.PanicMatches, false, "Function must take zero arguments", 1, "BOOM")
// Plain strings.
testCheck(c, check.PanicMatches, true, "", func() { panic("BOOM") }, "BO.M")
testCheck(c, check.PanicMatches, false, "", func() { panic("KABOOM") }, "BOOM")
testCheck(c, check.PanicMatches, true, "", func() bool { panic("BOOM") }, "BO.M")
// Verify params/names mutation
params, names := testCheck(c, check.PanicMatches, false, "", func() { panic(errors.New("KABOOM")) }, "BOOM")
c.Assert(params[0], check.Equals, "KABOOM")
c.Assert(names[0], check.Equals, "panic")
// Verify a nil panic
testCheck(c, check.PanicMatches, false, "Panic value is not a string or an error", func() { panic(nil) }, "")
}
func (s *CheckersS) TestFitsTypeOf(c *check.C) {
testInfo(c, check.FitsTypeOf, "FitsTypeOf", []string{"obtained", "sample"})
// Basic types
testCheck(c, check.FitsTypeOf, true, "", 1, 0)
testCheck(c, check.FitsTypeOf, false, "", 1, int64(0))
// Aliases
testCheck(c, check.FitsTypeOf, false, "", 1, errors.New(""))
testCheck(c, check.FitsTypeOf, false, "", "error", errors.New(""))
testCheck(c, check.FitsTypeOf, true, "", errors.New("error"), errors.New(""))
// Structures
testCheck(c, check.FitsTypeOf, false, "", 1, simpleStruct{})
testCheck(c, check.FitsTypeOf, false, "", simpleStruct{42}, &simpleStruct{})
testCheck(c, check.FitsTypeOf, true, "", simpleStruct{42}, simpleStruct{})
testCheck(c, check.FitsTypeOf, true, "", &simpleStruct{42}, &simpleStruct{})
// Some bad values
testCheck(c, check.FitsTypeOf, false, "Invalid sample value", 1, interface{}(nil))
testCheck(c, check.FitsTypeOf, false, "", interface{}(nil), 0)
}
func (s *CheckersS) TestImplements(c *check.C) {
testInfo(c, check.Implements, "Implements", []string{"obtained", "ifaceptr"})
var e error
var re runtime.Error
testCheck(c, check.Implements, true, "", errors.New(""), &e)
testCheck(c, check.Implements, false, "", errors.New(""), &re)
// Some bad values
testCheck(c, check.Implements, false, "ifaceptr should be a pointer to an interface variable", 0, errors.New(""))
testCheck(c, check.Implements, false, "ifaceptr should be a pointer to an interface variable", 0, interface{}(nil))
testCheck(c, check.Implements, false, "", interface{}(nil), &e)
}

View File

@ -1,19 +0,0 @@
package check
import "io"
func PrintLine(filename string, line int) (string, error) {
return printLine(filename, line)
}
func Indent(s, with string) string {
return indent(s, with)
}
func NewOutputWriter(writer io.Writer, stream, verbose bool) *outputWriter {
return newOutputWriter(writer, stream, verbose)
}
func (c *C) FakeSkip(reason string) {
c.reason = reason
}

View File

@ -1,484 +0,0 @@
// Tests for the behavior of the test fixture system.
package check_test
import (
. "gitee.com/johng/gf/third/gopkg.in/check.v1"
)
// -----------------------------------------------------------------------
// Fixture test suite.
type FixtureS struct{}
var fixtureS = Suite(&FixtureS{})
func (s *FixtureS) TestCountSuite(c *C) {
suitesRun += 1
}
// -----------------------------------------------------------------------
// Basic fixture ordering verification.
func (s *FixtureS) TestOrder(c *C) {
helper := FixtureHelper{}
Run(&helper, nil)
c.Check(helper.calls[0], Equals, "SetUpSuite")
c.Check(helper.calls[1], Equals, "SetUpTest")
c.Check(helper.calls[2], Equals, "Test1")
c.Check(helper.calls[3], Equals, "TearDownTest")
c.Check(helper.calls[4], Equals, "SetUpTest")
c.Check(helper.calls[5], Equals, "Test2")
c.Check(helper.calls[6], Equals, "TearDownTest")
c.Check(helper.calls[7], Equals, "TearDownSuite")
c.Check(len(helper.calls), Equals, 8)
}
// -----------------------------------------------------------------------
// Check the behavior when panics occur within tests and fixtures.
func (s *FixtureS) TestPanicOnTest(c *C) {
helper := FixtureHelper{panicOn: "Test1"}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Check(helper.calls[0], Equals, "SetUpSuite")
c.Check(helper.calls[1], Equals, "SetUpTest")
c.Check(helper.calls[2], Equals, "Test1")
c.Check(helper.calls[3], Equals, "TearDownTest")
c.Check(helper.calls[4], Equals, "SetUpTest")
c.Check(helper.calls[5], Equals, "Test2")
c.Check(helper.calls[6], Equals, "TearDownTest")
c.Check(helper.calls[7], Equals, "TearDownSuite")
c.Check(len(helper.calls), Equals, 8)
expected := "^\n-+\n" +
"PANIC: check_test\\.go:[0-9]+: FixtureHelper.Test1\n\n" +
"\\.\\.\\. Panic: Test1 \\(PC=[xA-F0-9]+\\)\n\n" +
".+:[0-9]+\n" +
" in (go)?panic\n" +
".*check_test.go:[0-9]+\n" +
" in FixtureHelper.trace\n" +
".*check_test.go:[0-9]+\n" +
" in FixtureHelper.Test1\n" +
"(.|\n)*$"
c.Check(output.value, Matches, expected)
}
func (s *FixtureS) TestPanicOnSetUpTest(c *C) {
helper := FixtureHelper{panicOn: "SetUpTest"}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Check(helper.calls[0], Equals, "SetUpSuite")
c.Check(helper.calls[1], Equals, "SetUpTest")
c.Check(helper.calls[2], Equals, "TearDownTest")
c.Check(helper.calls[3], Equals, "TearDownSuite")
c.Check(len(helper.calls), Equals, 4)
expected := "^\n-+\n" +
"PANIC: check_test\\.go:[0-9]+: " +
"FixtureHelper\\.SetUpTest\n\n" +
"\\.\\.\\. Panic: SetUpTest \\(PC=[xA-F0-9]+\\)\n\n" +
".+:[0-9]+\n" +
" in (go)?panic\n" +
".*check_test.go:[0-9]+\n" +
" in FixtureHelper.trace\n" +
".*check_test.go:[0-9]+\n" +
" in FixtureHelper.SetUpTest\n" +
"(.|\n)*" +
"\n-+\n" +
"PANIC: check_test\\.go:[0-9]+: " +
"FixtureHelper\\.Test1\n\n" +
"\\.\\.\\. Panic: Fixture has panicked " +
"\\(see related PANIC\\)\n$"
c.Check(output.value, Matches, expected)
}
func (s *FixtureS) TestPanicOnTearDownTest(c *C) {
helper := FixtureHelper{panicOn: "TearDownTest"}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Check(helper.calls[0], Equals, "SetUpSuite")
c.Check(helper.calls[1], Equals, "SetUpTest")
c.Check(helper.calls[2], Equals, "Test1")
c.Check(helper.calls[3], Equals, "TearDownTest")
c.Check(helper.calls[4], Equals, "TearDownSuite")
c.Check(len(helper.calls), Equals, 5)
expected := "^\n-+\n" +
"PANIC: check_test\\.go:[0-9]+: " +
"FixtureHelper.TearDownTest\n\n" +
"\\.\\.\\. Panic: TearDownTest \\(PC=[xA-F0-9]+\\)\n\n" +
".+:[0-9]+\n" +
" in (go)?panic\n" +
".*check_test.go:[0-9]+\n" +
" in FixtureHelper.trace\n" +
".*check_test.go:[0-9]+\n" +
" in FixtureHelper.TearDownTest\n" +
"(.|\n)*" +
"\n-+\n" +
"PANIC: check_test\\.go:[0-9]+: " +
"FixtureHelper\\.Test1\n\n" +
"\\.\\.\\. Panic: Fixture has panicked " +
"\\(see related PANIC\\)\n$"
c.Check(output.value, Matches, expected)
}
func (s *FixtureS) TestPanicOnSetUpSuite(c *C) {
helper := FixtureHelper{panicOn: "SetUpSuite"}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Check(helper.calls[0], Equals, "SetUpSuite")
c.Check(helper.calls[1], Equals, "TearDownSuite")
c.Check(len(helper.calls), Equals, 2)
expected := "^\n-+\n" +
"PANIC: check_test\\.go:[0-9]+: " +
"FixtureHelper.SetUpSuite\n\n" +
"\\.\\.\\. Panic: SetUpSuite \\(PC=[xA-F0-9]+\\)\n\n" +
".+:[0-9]+\n" +
" in (go)?panic\n" +
".*check_test.go:[0-9]+\n" +
" in FixtureHelper.trace\n" +
".*check_test.go:[0-9]+\n" +
" in FixtureHelper.SetUpSuite\n" +
"(.|\n)*$"
c.Check(output.value, Matches, expected)
}
func (s *FixtureS) TestPanicOnTearDownSuite(c *C) {
helper := FixtureHelper{panicOn: "TearDownSuite"}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Check(helper.calls[0], Equals, "SetUpSuite")
c.Check(helper.calls[1], Equals, "SetUpTest")
c.Check(helper.calls[2], Equals, "Test1")
c.Check(helper.calls[3], Equals, "TearDownTest")
c.Check(helper.calls[4], Equals, "SetUpTest")
c.Check(helper.calls[5], Equals, "Test2")
c.Check(helper.calls[6], Equals, "TearDownTest")
c.Check(helper.calls[7], Equals, "TearDownSuite")
c.Check(len(helper.calls), Equals, 8)
expected := "^\n-+\n" +
"PANIC: check_test\\.go:[0-9]+: " +
"FixtureHelper.TearDownSuite\n\n" +
"\\.\\.\\. Panic: TearDownSuite \\(PC=[xA-F0-9]+\\)\n\n" +
".+:[0-9]+\n" +
" in (go)?panic\n" +
".*check_test.go:[0-9]+\n" +
" in FixtureHelper.trace\n" +
".*check_test.go:[0-9]+\n" +
" in FixtureHelper.TearDownSuite\n" +
"(.|\n)*$"
c.Check(output.value, Matches, expected)
}
// -----------------------------------------------------------------------
// A wrong argument on a test or fixture will produce a nice error.
func (s *FixtureS) TestPanicOnWrongTestArg(c *C) {
helper := WrongTestArgHelper{}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Check(helper.calls[0], Equals, "SetUpSuite")
c.Check(helper.calls[1], Equals, "SetUpTest")
c.Check(helper.calls[2], Equals, "TearDownTest")
c.Check(helper.calls[3], Equals, "SetUpTest")
c.Check(helper.calls[4], Equals, "Test2")
c.Check(helper.calls[5], Equals, "TearDownTest")
c.Check(helper.calls[6], Equals, "TearDownSuite")
c.Check(len(helper.calls), Equals, 7)
expected := "^\n-+\n" +
"PANIC: fixture_test\\.go:[0-9]+: " +
"WrongTestArgHelper\\.Test1\n\n" +
"\\.\\.\\. Panic: WrongTestArgHelper\\.Test1 argument " +
"should be \\*check\\.C\n"
c.Check(output.value, Matches, expected)
}
func (s *FixtureS) TestPanicOnWrongSetUpTestArg(c *C) {
helper := WrongSetUpTestArgHelper{}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Check(len(helper.calls), Equals, 0)
expected :=
"^\n-+\n" +
"PANIC: fixture_test\\.go:[0-9]+: " +
"WrongSetUpTestArgHelper\\.SetUpTest\n\n" +
"\\.\\.\\. Panic: WrongSetUpTestArgHelper\\.SetUpTest argument " +
"should be \\*check\\.C\n"
c.Check(output.value, Matches, expected)
}
func (s *FixtureS) TestPanicOnWrongSetUpSuiteArg(c *C) {
helper := WrongSetUpSuiteArgHelper{}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Check(len(helper.calls), Equals, 0)
expected :=
"^\n-+\n" +
"PANIC: fixture_test\\.go:[0-9]+: " +
"WrongSetUpSuiteArgHelper\\.SetUpSuite\n\n" +
"\\.\\.\\. Panic: WrongSetUpSuiteArgHelper\\.SetUpSuite argument " +
"should be \\*check\\.C\n"
c.Check(output.value, Matches, expected)
}
// -----------------------------------------------------------------------
// Nice errors also when tests or fixture have wrong arg count.
func (s *FixtureS) TestPanicOnWrongTestArgCount(c *C) {
helper := WrongTestArgCountHelper{}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Check(helper.calls[0], Equals, "SetUpSuite")
c.Check(helper.calls[1], Equals, "SetUpTest")
c.Check(helper.calls[2], Equals, "TearDownTest")
c.Check(helper.calls[3], Equals, "SetUpTest")
c.Check(helper.calls[4], Equals, "Test2")
c.Check(helper.calls[5], Equals, "TearDownTest")
c.Check(helper.calls[6], Equals, "TearDownSuite")
c.Check(len(helper.calls), Equals, 7)
expected := "^\n-+\n" +
"PANIC: fixture_test\\.go:[0-9]+: " +
"WrongTestArgCountHelper\\.Test1\n\n" +
"\\.\\.\\. Panic: WrongTestArgCountHelper\\.Test1 argument " +
"should be \\*check\\.C\n"
c.Check(output.value, Matches, expected)
}
func (s *FixtureS) TestPanicOnWrongSetUpTestArgCount(c *C) {
helper := WrongSetUpTestArgCountHelper{}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Check(len(helper.calls), Equals, 0)
expected :=
"^\n-+\n" +
"PANIC: fixture_test\\.go:[0-9]+: " +
"WrongSetUpTestArgCountHelper\\.SetUpTest\n\n" +
"\\.\\.\\. Panic: WrongSetUpTestArgCountHelper\\.SetUpTest argument " +
"should be \\*check\\.C\n"
c.Check(output.value, Matches, expected)
}
func (s *FixtureS) TestPanicOnWrongSetUpSuiteArgCount(c *C) {
helper := WrongSetUpSuiteArgCountHelper{}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Check(len(helper.calls), Equals, 0)
expected :=
"^\n-+\n" +
"PANIC: fixture_test\\.go:[0-9]+: " +
"WrongSetUpSuiteArgCountHelper\\.SetUpSuite\n\n" +
"\\.\\.\\. Panic: WrongSetUpSuiteArgCountHelper" +
"\\.SetUpSuite argument should be \\*check\\.C\n"
c.Check(output.value, Matches, expected)
}
// -----------------------------------------------------------------------
// Helper test suites with wrong function arguments.
type WrongTestArgHelper struct {
FixtureHelper
}
func (s *WrongTestArgHelper) Test1(t int) {
}
type WrongSetUpTestArgHelper struct {
FixtureHelper
}
func (s *WrongSetUpTestArgHelper) SetUpTest(t int) {
}
type WrongSetUpSuiteArgHelper struct {
FixtureHelper
}
func (s *WrongSetUpSuiteArgHelper) SetUpSuite(t int) {
}
type WrongTestArgCountHelper struct {
FixtureHelper
}
func (s *WrongTestArgCountHelper) Test1(c *C, i int) {
}
type WrongSetUpTestArgCountHelper struct {
FixtureHelper
}
func (s *WrongSetUpTestArgCountHelper) SetUpTest(c *C, i int) {
}
type WrongSetUpSuiteArgCountHelper struct {
FixtureHelper
}
func (s *WrongSetUpSuiteArgCountHelper) SetUpSuite(c *C, i int) {
}
// -----------------------------------------------------------------------
// Ensure fixture doesn't run without tests.
type NoTestsHelper struct {
hasRun bool
}
func (s *NoTestsHelper) SetUpSuite(c *C) {
s.hasRun = true
}
func (s *NoTestsHelper) TearDownSuite(c *C) {
s.hasRun = true
}
func (s *FixtureS) TestFixtureDoesntRunWithoutTests(c *C) {
helper := NoTestsHelper{}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Check(helper.hasRun, Equals, false)
}
// -----------------------------------------------------------------------
// Verify that checks and assertions work correctly inside the fixture.
type FixtureCheckHelper struct {
fail string
completed bool
}
func (s *FixtureCheckHelper) SetUpSuite(c *C) {
switch s.fail {
case "SetUpSuiteAssert":
c.Assert(false, Equals, true)
case "SetUpSuiteCheck":
c.Check(false, Equals, true)
}
s.completed = true
}
func (s *FixtureCheckHelper) SetUpTest(c *C) {
switch s.fail {
case "SetUpTestAssert":
c.Assert(false, Equals, true)
case "SetUpTestCheck":
c.Check(false, Equals, true)
}
s.completed = true
}
func (s *FixtureCheckHelper) Test(c *C) {
// Do nothing.
}
func (s *FixtureS) TestSetUpSuiteCheck(c *C) {
helper := FixtureCheckHelper{fail: "SetUpSuiteCheck"}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Assert(output.value, Matches,
"\n---+\n"+
"FAIL: fixture_test\\.go:[0-9]+: "+
"FixtureCheckHelper\\.SetUpSuite\n\n"+
"fixture_test\\.go:[0-9]+:\n"+
" c\\.Check\\(false, Equals, true\\)\n"+
"\\.+ obtained bool = false\n"+
"\\.+ expected bool = true\n\n")
c.Assert(helper.completed, Equals, true)
}
func (s *FixtureS) TestSetUpSuiteAssert(c *C) {
helper := FixtureCheckHelper{fail: "SetUpSuiteAssert"}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Assert(output.value, Matches,
"\n---+\n"+
"FAIL: fixture_test\\.go:[0-9]+: "+
"FixtureCheckHelper\\.SetUpSuite\n\n"+
"fixture_test\\.go:[0-9]+:\n"+
" c\\.Assert\\(false, Equals, true\\)\n"+
"\\.+ obtained bool = false\n"+
"\\.+ expected bool = true\n\n")
c.Assert(helper.completed, Equals, false)
}
// -----------------------------------------------------------------------
// Verify that logging within SetUpTest() persists within the test log itself.
type FixtureLogHelper struct {
c *C
}
func (s *FixtureLogHelper) SetUpTest(c *C) {
s.c = c
c.Log("1")
}
func (s *FixtureLogHelper) Test(c *C) {
c.Log("2")
s.c.Log("3")
c.Log("4")
c.Fail()
}
func (s *FixtureLogHelper) TearDownTest(c *C) {
s.c.Log("5")
}
func (s *FixtureS) TestFixtureLogging(c *C) {
helper := FixtureLogHelper{}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Assert(output.value, Matches,
"\n---+\n"+
"FAIL: fixture_test\\.go:[0-9]+: "+
"FixtureLogHelper\\.Test\n\n"+
"1\n2\n3\n4\n5\n")
}
// -----------------------------------------------------------------------
// Skip() within fixture methods.
func (s *FixtureS) TestSkipSuite(c *C) {
helper := FixtureHelper{skip: true, skipOnN: 0}
output := String{}
result := Run(&helper, &RunConf{Output: &output})
c.Assert(output.value, Equals, "")
c.Assert(helper.calls[0], Equals, "SetUpSuite")
c.Assert(helper.calls[1], Equals, "TearDownSuite")
c.Assert(len(helper.calls), Equals, 2)
c.Assert(result.Skipped, Equals, 2)
}
func (s *FixtureS) TestSkipTest(c *C) {
helper := FixtureHelper{skip: true, skipOnN: 1}
output := String{}
result := Run(&helper, &RunConf{Output: &output})
c.Assert(helper.calls[0], Equals, "SetUpSuite")
c.Assert(helper.calls[1], Equals, "SetUpTest")
c.Assert(helper.calls[2], Equals, "SetUpTest")
c.Assert(helper.calls[3], Equals, "Test2")
c.Assert(helper.calls[4], Equals, "TearDownTest")
c.Assert(helper.calls[5], Equals, "TearDownSuite")
c.Assert(len(helper.calls), Equals, 6)
c.Assert(result.Skipped, Equals, 1)
}

View File

@ -1,335 +0,0 @@
// These tests check that the foundations of gocheck are working properly.
// They already assume that fundamental failing is working already, though,
// since this was tested in bootstrap_test.go. Even then, some care may
// still have to be taken when using external functions, since they should
// of course not rely on functionality tested here.
package check_test
import (
"fmt"
"gitee.com/johng/gf/third/gopkg.in/check.v1"
"log"
"os"
"regexp"
"strings"
)
// -----------------------------------------------------------------------
// Foundation test suite.
type FoundationS struct{}
var foundationS = check.Suite(&FoundationS{})
func (s *FoundationS) TestCountSuite(c *check.C) {
suitesRun += 1
}
func (s *FoundationS) TestErrorf(c *check.C) {
// Do not use checkState() here. It depends on Errorf() working.
expectedLog := fmt.Sprintf("foundation_test.go:%d:\n"+
" c.Errorf(\"Error %%v!\", \"message\")\n"+
"... Error: Error message!\n\n",
getMyLine()+1)
c.Errorf("Error %v!", "message")
failed := c.Failed()
c.Succeed()
if log := c.GetTestLog(); log != expectedLog {
c.Logf("Errorf() logged %#v rather than %#v", log, expectedLog)
c.Fail()
}
if !failed {
c.Logf("Errorf() didn't put the test in a failed state")
c.Fail()
}
}
func (s *FoundationS) TestError(c *check.C) {
expectedLog := fmt.Sprintf("foundation_test.go:%d:\n"+
" c\\.Error\\(\"Error \", \"message!\"\\)\n"+
"\\.\\.\\. Error: Error message!\n\n",
getMyLine()+1)
c.Error("Error ", "message!")
checkState(c, nil,
&expectedState{
name: "Error(`Error `, `message!`)",
failed: true,
log: expectedLog,
})
}
func (s *FoundationS) TestFailNow(c *check.C) {
defer (func() {
if !c.Failed() {
c.Error("FailNow() didn't fail the test")
} else {
c.Succeed()
if c.GetTestLog() != "" {
c.Error("Something got logged:\n" + c.GetTestLog())
}
}
})()
c.FailNow()
c.Log("FailNow() didn't stop the test")
}
func (s *FoundationS) TestSucceedNow(c *check.C) {
defer (func() {
if c.Failed() {
c.Error("SucceedNow() didn't succeed the test")
}
if c.GetTestLog() != "" {
c.Error("Something got logged:\n" + c.GetTestLog())
}
})()
c.Fail()
c.SucceedNow()
c.Log("SucceedNow() didn't stop the test")
}
func (s *FoundationS) TestFailureHeader(c *check.C) {
output := String{}
failHelper := FailHelper{}
check.Run(&failHelper, &check.RunConf{Output: &output})
header := fmt.Sprintf(""+
"\n-----------------------------------"+
"-----------------------------------\n"+
"FAIL: check_test.go:%d: FailHelper.TestLogAndFail\n",
failHelper.testLine)
if strings.Index(output.value, header) == -1 {
c.Errorf(""+
"Failure didn't print a proper header.\n"+
"... Got:\n%s... Expected something with:\n%s",
output.value, header)
}
}
func (s *FoundationS) TestFatal(c *check.C) {
var line int
defer (func() {
if !c.Failed() {
c.Error("Fatal() didn't fail the test")
} else {
c.Succeed()
expected := fmt.Sprintf("foundation_test.go:%d:\n"+
" c.Fatal(\"Die \", \"now!\")\n"+
"... Error: Die now!\n\n",
line)
if c.GetTestLog() != expected {
c.Error("Incorrect log:", c.GetTestLog())
}
}
})()
line = getMyLine() + 1
c.Fatal("Die ", "now!")
c.Log("Fatal() didn't stop the test")
}
func (s *FoundationS) TestFatalf(c *check.C) {
var line int
defer (func() {
if !c.Failed() {
c.Error("Fatalf() didn't fail the test")
} else {
c.Succeed()
expected := fmt.Sprintf("foundation_test.go:%d:\n"+
" c.Fatalf(\"Die %%s!\", \"now\")\n"+
"... Error: Die now!\n\n",
line)
if c.GetTestLog() != expected {
c.Error("Incorrect log:", c.GetTestLog())
}
}
})()
line = getMyLine() + 1
c.Fatalf("Die %s!", "now")
c.Log("Fatalf() didn't stop the test")
}
func (s *FoundationS) TestCallerLoggingInsideTest(c *check.C) {
log := fmt.Sprintf(""+
"foundation_test.go:%d:\n"+
" result := c.Check\\(10, check.Equals, 20\\)\n"+
"\\.\\.\\. obtained int = 10\n"+
"\\.\\.\\. expected int = 20\n\n",
getMyLine()+1)
result := c.Check(10, check.Equals, 20)
checkState(c, result,
&expectedState{
name: "Check(10, Equals, 20)",
result: false,
failed: true,
log: log,
})
}
func (s *FoundationS) TestCallerLoggingInDifferentFile(c *check.C) {
result, line := checkEqualWrapper(c, 10, 20)
testLine := getMyLine() - 1
log := fmt.Sprintf(""+
"foundation_test.go:%d:\n"+
" result, line := checkEqualWrapper\\(c, 10, 20\\)\n"+
"check_test.go:%d:\n"+
" return c.Check\\(obtained, check.Equals, expected\\), getMyLine\\(\\)\n"+
"\\.\\.\\. obtained int = 10\n"+
"\\.\\.\\. expected int = 20\n\n",
testLine, line)
checkState(c, result,
&expectedState{
name: "Check(10, Equals, 20)",
result: false,
failed: true,
log: log,
})
}
// -----------------------------------------------------------------------
// ExpectFailure() inverts the logic of failure.
type ExpectFailureSucceedHelper struct{}
func (s *ExpectFailureSucceedHelper) TestSucceed(c *check.C) {
c.ExpectFailure("It booms!")
c.Error("Boom!")
}
type ExpectFailureFailHelper struct{}
func (s *ExpectFailureFailHelper) TestFail(c *check.C) {
c.ExpectFailure("Bug #XYZ")
}
func (s *FoundationS) TestExpectFailureFail(c *check.C) {
helper := ExpectFailureFailHelper{}
output := String{}
result := check.Run(&helper, &check.RunConf{Output: &output})
expected := "" +
"^\n-+\n" +
"FAIL: foundation_test\\.go:[0-9]+:" +
" ExpectFailureFailHelper\\.TestFail\n\n" +
"\\.\\.\\. Error: Test succeeded, but was expected to fail\n" +
"\\.\\.\\. Reason: Bug #XYZ\n$"
matched, err := regexp.MatchString(expected, output.value)
if err != nil {
c.Error("Bad expression: ", expected)
} else if !matched {
c.Error("ExpectFailure() didn't log properly:\n", output.value)
}
c.Assert(result.ExpectedFailures, check.Equals, 0)
}
func (s *FoundationS) TestExpectFailureSucceed(c *check.C) {
helper := ExpectFailureSucceedHelper{}
output := String{}
result := check.Run(&helper, &check.RunConf{Output: &output})
c.Assert(output.value, check.Equals, "")
c.Assert(result.ExpectedFailures, check.Equals, 1)
}
func (s *FoundationS) TestExpectFailureSucceedVerbose(c *check.C) {
helper := ExpectFailureSucceedHelper{}
output := String{}
result := check.Run(&helper, &check.RunConf{Output: &output, Verbose: true})
expected := "" +
"FAIL EXPECTED: foundation_test\\.go:[0-9]+:" +
" ExpectFailureSucceedHelper\\.TestSucceed \\(It booms!\\)\t *[.0-9]+s\n"
matched, err := regexp.MatchString(expected, output.value)
if err != nil {
c.Error("Bad expression: ", expected)
} else if !matched {
c.Error("ExpectFailure() didn't log properly:\n", output.value)
}
c.Assert(result.ExpectedFailures, check.Equals, 1)
}
// -----------------------------------------------------------------------
// Skip() allows stopping a test without positive/negative results.
type SkipTestHelper struct{}
func (s *SkipTestHelper) TestFail(c *check.C) {
c.Skip("Wrong platform or whatever")
c.Error("Boom!")
}
func (s *FoundationS) TestSkip(c *check.C) {
helper := SkipTestHelper{}
output := String{}
check.Run(&helper, &check.RunConf{Output: &output})
if output.value != "" {
c.Error("Skip() logged something:\n", output.value)
}
}
func (s *FoundationS) TestSkipVerbose(c *check.C) {
helper := SkipTestHelper{}
output := String{}
check.Run(&helper, &check.RunConf{Output: &output, Verbose: true})
expected := "SKIP: foundation_test\\.go:[0-9]+: SkipTestHelper\\.TestFail" +
" \\(Wrong platform or whatever\\)"
matched, err := regexp.MatchString(expected, output.value)
if err != nil {
c.Error("Bad expression: ", expected)
} else if !matched {
c.Error("Skip() didn't log properly:\n", output.value)
}
}
// -----------------------------------------------------------------------
// Check minimum *log.Logger interface provided by *check.C.
type minLogger interface {
Output(calldepth int, s string) error
}
func (s *BootstrapS) TestMinLogger(c *check.C) {
var logger minLogger
logger = log.New(os.Stderr, "", 0)
logger = c
logger.Output(0, "Hello there")
expected := `\[LOG\] [0-9]+:[0-9][0-9]\.[0-9][0-9][0-9] +Hello there\n`
output := c.GetTestLog()
c.Assert(output, check.Matches, expected)
}
// -----------------------------------------------------------------------
// Ensure that suites with embedded types are working fine, including the
// the workaround for issue 906.
type EmbeddedInternalS struct {
called bool
}
type EmbeddedS struct {
EmbeddedInternalS
}
var embeddedS = check.Suite(&EmbeddedS{})
func (s *EmbeddedS) TestCountSuite(c *check.C) {
suitesRun += 1
}
func (s *EmbeddedInternalS) TestMethod(c *check.C) {
c.Error("TestMethod() of the embedded type was called!?")
}
func (s *EmbeddedS) TestMethod(c *check.C) {
// http://code.google.com/p/go/issues/detail?id=906
c.Check(s.called, check.Equals, false) // Go issue 906 is affecting the runner?
s.called = true
}

View File

@ -1 +0,0 @@
module check.v1

View File

@ -1,231 +0,0 @@
package check
import (
"fmt"
"strings"
"time"
)
// TestName returns the current test name in the form "SuiteName.TestName"
func (c *C) TestName() string {
return c.testName
}
// -----------------------------------------------------------------------
// Basic succeeding/failing logic.
// Failed returns whether the currently running test has already failed.
func (c *C) Failed() bool {
return c.status() == failedSt
}
// Fail marks the currently running test as failed.
//
// Something ought to have been previously logged so the developer can tell
// what went wrong. The higher level helper functions will fail the test
// and do the logging properly.
func (c *C) Fail() {
c.setStatus(failedSt)
}
// FailNow marks the currently running test as failed and stops running it.
// Something ought to have been previously logged so the developer can tell
// what went wrong. The higher level helper functions will fail the test
// and do the logging properly.
func (c *C) FailNow() {
c.Fail()
c.stopNow()
}
// Succeed marks the currently running test as succeeded, undoing any
// previous failures.
func (c *C) Succeed() {
c.setStatus(succeededSt)
}
// SucceedNow marks the currently running test as succeeded, undoing any
// previous failures, and stops running the test.
func (c *C) SucceedNow() {
c.Succeed()
c.stopNow()
}
// ExpectFailure informs that the running test is knowingly broken for
// the provided reason. If the test does not fail, an error will be reported
// to raise attention to this fact. This method is useful to temporarily
// disable tests which cover well known problems until a better time to
// fix the problem is found, without forgetting about the fact that a
// failure still exists.
func (c *C) ExpectFailure(reason string) {
if reason == "" {
panic("Missing reason why the test is expected to fail")
}
c.mustFail = true
c.reason = reason
}
// Skip skips the running test for the provided reason. If run from within
// SetUpTest, the individual test being set up will be skipped, and if run
// from within SetUpSuite, the whole suite is skipped.
func (c *C) Skip(reason string) {
if reason == "" {
panic("Missing reason why the test is being skipped")
}
c.reason = reason
c.setStatus(skippedSt)
c.stopNow()
}
// -----------------------------------------------------------------------
// Basic logging.
// GetTestLog returns the current test error output.
func (c *C) GetTestLog() string {
return c.logb.String()
}
// Log logs some information into the test error output.
// The provided arguments are assembled together into a string with fmt.Sprint.
func (c *C) Log(args ...interface{}) {
c.log(args...)
}
// Log logs some information into the test error output.
// The provided arguments are assembled together into a string with fmt.Sprintf.
func (c *C) Logf(format string, args ...interface{}) {
c.logf(format, args...)
}
// Output enables *C to be used as a logger in functions that require only
// the minimum interface of *log.Logger.
func (c *C) Output(calldepth int, s string) error {
d := time.Now().Sub(c.startTime)
msec := d / time.Millisecond
sec := d / time.Second
min := d / time.Minute
c.Logf("[LOG] %d:%02d.%03d %s", min, sec%60, msec%1000, s)
return nil
}
// Error logs an error into the test error output and marks the test as failed.
// The provided arguments are assembled together into a string with fmt.Sprint.
func (c *C) Error(args ...interface{}) {
c.logCaller(1)
c.logString(fmt.Sprint("Error: ", fmt.Sprint(args...)))
c.logNewLine()
c.Fail()
}
// Errorf logs an error into the test error output and marks the test as failed.
// The provided arguments are assembled together into a string with fmt.Sprintf.
func (c *C) Errorf(format string, args ...interface{}) {
c.logCaller(1)
c.logString(fmt.Sprintf("Error: "+format, args...))
c.logNewLine()
c.Fail()
}
// Fatal logs an error into the test error output, marks the test as failed, and
// stops the test execution. The provided arguments are assembled together into
// a string with fmt.Sprint.
func (c *C) Fatal(args ...interface{}) {
c.logCaller(1)
c.logString(fmt.Sprint("Error: ", fmt.Sprint(args...)))
c.logNewLine()
c.FailNow()
}
// Fatlaf logs an error into the test error output, marks the test as failed, and
// stops the test execution. The provided arguments are assembled together into
// a string with fmt.Sprintf.
func (c *C) Fatalf(format string, args ...interface{}) {
c.logCaller(1)
c.logString(fmt.Sprint("Error: ", fmt.Sprintf(format, args...)))
c.logNewLine()
c.FailNow()
}
// -----------------------------------------------------------------------
// Generic checks and assertions based on checkers.
// Check verifies if the first value matches the expected value according
// to the provided checker. If they do not match, an error is logged, the
// test is marked as failed, and the test execution continues.
//
// Some checkers may not need the expected argument (e.g. IsNil).
//
// Extra arguments provided to the function are logged next to the reported
// problem when the matching fails.
func (c *C) Check(obtained interface{}, checker Checker, args ...interface{}) bool {
return c.internalCheck("Check", obtained, checker, args...)
}
// Assert ensures that the first value matches the expected value according
// to the provided checker. If they do not match, an error is logged, the
// test is marked as failed, and the test execution stops.
//
// Some checkers may not need the expected argument (e.g. IsNil).
//
// Extra arguments provided to the function are logged next to the reported
// problem when the matching fails.
func (c *C) Assert(obtained interface{}, checker Checker, args ...interface{}) {
if !c.internalCheck("Assert", obtained, checker, args...) {
c.stopNow()
}
}
func (c *C) internalCheck(funcName string, obtained interface{}, checker Checker, args ...interface{}) bool {
if checker == nil {
c.logCaller(2)
c.logString(fmt.Sprintf("%s(obtained, nil!?, ...):", funcName))
c.logString("Oops.. you've provided a nil checker!")
c.logNewLine()
c.Fail()
return false
}
// If the last argument is a bug info, extract it out.
var comment CommentInterface
if len(args) > 0 {
if c, ok := args[len(args)-1].(CommentInterface); ok {
comment = c
args = args[:len(args)-1]
}
}
params := append([]interface{}{obtained}, args...)
info := checker.Info()
if len(params) != len(info.Params) {
names := append([]string{info.Params[0], info.Name}, info.Params[1:]...)
c.logCaller(2)
c.logString(fmt.Sprintf("%s(%s):", funcName, strings.Join(names, ", ")))
c.logString(fmt.Sprintf("Wrong number of parameters for %s: want %d, got %d", info.Name, len(names), len(params)+1))
c.logNewLine()
c.Fail()
return false
}
// Copy since it may be mutated by Check.
names := append([]string{}, info.Params...)
// Do the actual check.
result, error := checker.Check(params, names)
if !result || error != "" {
c.logCaller(2)
for i := 0; i != len(params); i++ {
c.logValue(names[i], params[i])
}
if comment != nil {
c.logString(comment.CheckCommentString())
}
if error != "" {
c.logString(error)
}
c.logNewLine()
c.Fail()
return false
}
return true
}

View File

@ -1,519 +0,0 @@
// These tests verify the inner workings of the helper methods associated
// with check.T.
package check_test
import (
"gitee.com/johng/gf/third/gopkg.in/check.v1"
"os"
"reflect"
"runtime"
"sync"
)
var helpersS = check.Suite(&HelpersS{})
type HelpersS struct{}
func (s *HelpersS) TestCountSuite(c *check.C) {
suitesRun += 1
}
// -----------------------------------------------------------------------
// Fake checker and bug info to verify the behavior of Assert() and Check().
type MyChecker struct {
info *check.CheckerInfo
params []interface{}
names []string
result bool
error string
}
func (checker *MyChecker) Info() *check.CheckerInfo {
if checker.info == nil {
return &check.CheckerInfo{Name: "MyChecker", Params: []string{"myobtained", "myexpected"}}
}
return checker.info
}
func (checker *MyChecker) Check(params []interface{}, names []string) (bool, string) {
rparams := checker.params
rnames := checker.names
checker.params = append([]interface{}{}, params...)
checker.names = append([]string{}, names...)
if rparams != nil {
copy(params, rparams)
}
if rnames != nil {
copy(names, rnames)
}
return checker.result, checker.error
}
type myCommentType string
func (c myCommentType) CheckCommentString() string {
return string(c)
}
func myComment(s string) myCommentType {
return myCommentType(s)
}
// -----------------------------------------------------------------------
// Ensure a real checker actually works fine.
func (s *HelpersS) TestCheckerInterface(c *check.C) {
testHelperSuccess(c, "Check(1, Equals, 1)", true, func() interface{} {
return c.Check(1, check.Equals, 1)
})
}
// -----------------------------------------------------------------------
// Tests for Check(), mostly the same as for Assert() following these.
func (s *HelpersS) TestCheckSucceedWithExpected(c *check.C) {
checker := &MyChecker{result: true}
testHelperSuccess(c, "Check(1, checker, 2)", true, func() interface{} {
return c.Check(1, checker, 2)
})
if !reflect.DeepEqual(checker.params, []interface{}{1, 2}) {
c.Fatalf("Bad params for check: %#v", checker.params)
}
}
func (s *HelpersS) TestCheckSucceedWithoutExpected(c *check.C) {
checker := &MyChecker{result: true, info: &check.CheckerInfo{Params: []string{"myvalue"}}}
testHelperSuccess(c, "Check(1, checker)", true, func() interface{} {
return c.Check(1, checker)
})
if !reflect.DeepEqual(checker.params, []interface{}{1}) {
c.Fatalf("Bad params for check: %#v", checker.params)
}
}
func (s *HelpersS) TestCheckFailWithExpected(c *check.C) {
checker := &MyChecker{result: false}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" return c\\.Check\\(1, checker, 2\\)\n" +
"\\.+ myobtained int = 1\n" +
"\\.+ myexpected int = 2\n\n"
testHelperFailure(c, "Check(1, checker, 2)", false, false, log,
func() interface{} {
return c.Check(1, checker, 2)
})
}
func (s *HelpersS) TestCheckFailWithExpectedAndComment(c *check.C) {
checker := &MyChecker{result: false}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" return c\\.Check\\(1, checker, 2, myComment\\(\"Hello world!\"\\)\\)\n" +
"\\.+ myobtained int = 1\n" +
"\\.+ myexpected int = 2\n" +
"\\.+ Hello world!\n\n"
testHelperFailure(c, "Check(1, checker, 2, msg)", false, false, log,
func() interface{} {
return c.Check(1, checker, 2, myComment("Hello world!"))
})
}
func (s *HelpersS) TestCheckFailWithExpectedAndStaticComment(c *check.C) {
checker := &MyChecker{result: false}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" // Nice leading comment\\.\n" +
" return c\\.Check\\(1, checker, 2\\) // Hello there\n" +
"\\.+ myobtained int = 1\n" +
"\\.+ myexpected int = 2\n\n"
testHelperFailure(c, "Check(1, checker, 2, msg)", false, false, log,
func() interface{} {
// Nice leading comment.
return c.Check(1, checker, 2) // Hello there
})
}
func (s *HelpersS) TestCheckFailWithoutExpected(c *check.C) {
checker := &MyChecker{result: false, info: &check.CheckerInfo{Params: []string{"myvalue"}}}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" return c\\.Check\\(1, checker\\)\n" +
"\\.+ myvalue int = 1\n\n"
testHelperFailure(c, "Check(1, checker)", false, false, log,
func() interface{} {
return c.Check(1, checker)
})
}
func (s *HelpersS) TestCheckFailWithoutExpectedAndMessage(c *check.C) {
checker := &MyChecker{result: false, info: &check.CheckerInfo{Params: []string{"myvalue"}}}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" return c\\.Check\\(1, checker, myComment\\(\"Hello world!\"\\)\\)\n" +
"\\.+ myvalue int = 1\n" +
"\\.+ Hello world!\n\n"
testHelperFailure(c, "Check(1, checker, msg)", false, false, log,
func() interface{} {
return c.Check(1, checker, myComment("Hello world!"))
})
}
func (s *HelpersS) TestCheckWithMissingExpected(c *check.C) {
checker := &MyChecker{result: true}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" return c\\.Check\\(1, checker\\)\n" +
"\\.+ Check\\(myobtained, MyChecker, myexpected\\):\n" +
"\\.+ Wrong number of parameters for MyChecker: " +
"want 3, got 2\n\n"
testHelperFailure(c, "Check(1, checker, !?)", false, false, log,
func() interface{} {
return c.Check(1, checker)
})
}
func (s *HelpersS) TestCheckWithTooManyExpected(c *check.C) {
checker := &MyChecker{result: true}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" return c\\.Check\\(1, checker, 2, 3\\)\n" +
"\\.+ Check\\(myobtained, MyChecker, myexpected\\):\n" +
"\\.+ Wrong number of parameters for MyChecker: " +
"want 3, got 4\n\n"
testHelperFailure(c, "Check(1, checker, 2, 3)", false, false, log,
func() interface{} {
return c.Check(1, checker, 2, 3)
})
}
func (s *HelpersS) TestCheckWithError(c *check.C) {
checker := &MyChecker{result: false, error: "Some not so cool data provided!"}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" return c\\.Check\\(1, checker, 2\\)\n" +
"\\.+ myobtained int = 1\n" +
"\\.+ myexpected int = 2\n" +
"\\.+ Some not so cool data provided!\n\n"
testHelperFailure(c, "Check(1, checker, 2)", false, false, log,
func() interface{} {
return c.Check(1, checker, 2)
})
}
func (s *HelpersS) TestCheckWithNilChecker(c *check.C) {
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" return c\\.Check\\(1, nil\\)\n" +
"\\.+ Check\\(obtained, nil!\\?, \\.\\.\\.\\):\n" +
"\\.+ Oops\\.\\. you've provided a nil checker!\n\n"
testHelperFailure(c, "Check(obtained, nil)", false, false, log,
func() interface{} {
return c.Check(1, nil)
})
}
func (s *HelpersS) TestCheckWithParamsAndNamesMutation(c *check.C) {
checker := &MyChecker{result: false, params: []interface{}{3, 4}, names: []string{"newobtained", "newexpected"}}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" return c\\.Check\\(1, checker, 2\\)\n" +
"\\.+ newobtained int = 3\n" +
"\\.+ newexpected int = 4\n\n"
testHelperFailure(c, "Check(1, checker, 2) with mutation", false, false, log,
func() interface{} {
return c.Check(1, checker, 2)
})
}
// -----------------------------------------------------------------------
// Tests for Assert(), mostly the same as for Check() above.
func (s *HelpersS) TestAssertSucceedWithExpected(c *check.C) {
checker := &MyChecker{result: true}
testHelperSuccess(c, "Assert(1, checker, 2)", nil, func() interface{} {
c.Assert(1, checker, 2)
return nil
})
if !reflect.DeepEqual(checker.params, []interface{}{1, 2}) {
c.Fatalf("Bad params for check: %#v", checker.params)
}
}
func (s *HelpersS) TestAssertSucceedWithoutExpected(c *check.C) {
checker := &MyChecker{result: true, info: &check.CheckerInfo{Params: []string{"myvalue"}}}
testHelperSuccess(c, "Assert(1, checker)", nil, func() interface{} {
c.Assert(1, checker)
return nil
})
if !reflect.DeepEqual(checker.params, []interface{}{1}) {
c.Fatalf("Bad params for check: %#v", checker.params)
}
}
func (s *HelpersS) TestAssertFailWithExpected(c *check.C) {
checker := &MyChecker{result: false}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" c\\.Assert\\(1, checker, 2\\)\n" +
"\\.+ myobtained int = 1\n" +
"\\.+ myexpected int = 2\n\n"
testHelperFailure(c, "Assert(1, checker, 2)", nil, true, log,
func() interface{} {
c.Assert(1, checker, 2)
return nil
})
}
func (s *HelpersS) TestAssertFailWithExpectedAndMessage(c *check.C) {
checker := &MyChecker{result: false}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" c\\.Assert\\(1, checker, 2, myComment\\(\"Hello world!\"\\)\\)\n" +
"\\.+ myobtained int = 1\n" +
"\\.+ myexpected int = 2\n" +
"\\.+ Hello world!\n\n"
testHelperFailure(c, "Assert(1, checker, 2, msg)", nil, true, log,
func() interface{} {
c.Assert(1, checker, 2, myComment("Hello world!"))
return nil
})
}
func (s *HelpersS) TestAssertFailWithoutExpected(c *check.C) {
checker := &MyChecker{result: false, info: &check.CheckerInfo{Params: []string{"myvalue"}}}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" c\\.Assert\\(1, checker\\)\n" +
"\\.+ myvalue int = 1\n\n"
testHelperFailure(c, "Assert(1, checker)", nil, true, log,
func() interface{} {
c.Assert(1, checker)
return nil
})
}
func (s *HelpersS) TestAssertFailWithoutExpectedAndMessage(c *check.C) {
checker := &MyChecker{result: false, info: &check.CheckerInfo{Params: []string{"myvalue"}}}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" c\\.Assert\\(1, checker, myComment\\(\"Hello world!\"\\)\\)\n" +
"\\.+ myvalue int = 1\n" +
"\\.+ Hello world!\n\n"
testHelperFailure(c, "Assert(1, checker, msg)", nil, true, log,
func() interface{} {
c.Assert(1, checker, myComment("Hello world!"))
return nil
})
}
func (s *HelpersS) TestAssertWithMissingExpected(c *check.C) {
checker := &MyChecker{result: true}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" c\\.Assert\\(1, checker\\)\n" +
"\\.+ Assert\\(myobtained, MyChecker, myexpected\\):\n" +
"\\.+ Wrong number of parameters for MyChecker: " +
"want 3, got 2\n\n"
testHelperFailure(c, "Assert(1, checker, !?)", nil, true, log,
func() interface{} {
c.Assert(1, checker)
return nil
})
}
func (s *HelpersS) TestAssertWithError(c *check.C) {
checker := &MyChecker{result: false, error: "Some not so cool data provided!"}
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" c\\.Assert\\(1, checker, 2\\)\n" +
"\\.+ myobtained int = 1\n" +
"\\.+ myexpected int = 2\n" +
"\\.+ Some not so cool data provided!\n\n"
testHelperFailure(c, "Assert(1, checker, 2)", nil, true, log,
func() interface{} {
c.Assert(1, checker, 2)
return nil
})
}
func (s *HelpersS) TestAssertWithNilChecker(c *check.C) {
log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" +
" c\\.Assert\\(1, nil\\)\n" +
"\\.+ Assert\\(obtained, nil!\\?, \\.\\.\\.\\):\n" +
"\\.+ Oops\\.\\. you've provided a nil checker!\n\n"
testHelperFailure(c, "Assert(obtained, nil)", nil, true, log,
func() interface{} {
c.Assert(1, nil)
return nil
})
}
// -----------------------------------------------------------------------
// Ensure that values logged work properly in some interesting cases.
func (s *HelpersS) TestValueLoggingWithArrays(c *check.C) {
checker := &MyChecker{result: false}
log := "(?s)helpers_test.go:[0-9]+:.*\nhelpers_test.go:[0-9]+:\n" +
" return c\\.Check\\(\\[\\]byte{1, 2}, checker, \\[\\]byte{1, 3}\\)\n" +
"\\.+ myobtained \\[\\]uint8 = \\[\\]byte{0x1, 0x2}\n" +
"\\.+ myexpected \\[\\]uint8 = \\[\\]byte{0x1, 0x3}\n\n"
testHelperFailure(c, "Check([]byte{1}, chk, []byte{3})", false, false, log,
func() interface{} {
return c.Check([]byte{1, 2}, checker, []byte{1, 3})
})
}
func (s *HelpersS) TestValueLoggingWithMultiLine(c *check.C) {
checker := &MyChecker{result: false}
log := "(?s)helpers_test.go:[0-9]+:.*\nhelpers_test.go:[0-9]+:\n" +
" return c\\.Check\\(\"a\\\\nb\\\\n\", checker, \"a\\\\nb\\\\nc\"\\)\n" +
"\\.+ myobtained string = \"\" \\+\n" +
"\\.+ \"a\\\\n\" \\+\n" +
"\\.+ \"b\\\\n\"\n" +
"\\.+ myexpected string = \"\" \\+\n" +
"\\.+ \"a\\\\n\" \\+\n" +
"\\.+ \"b\\\\n\" \\+\n" +
"\\.+ \"c\"\n\n"
testHelperFailure(c, `Check("a\nb\n", chk, "a\nb\nc")`, false, false, log,
func() interface{} {
return c.Check("a\nb\n", checker, "a\nb\nc")
})
}
func (s *HelpersS) TestValueLoggingWithMultiLineException(c *check.C) {
// If the newline is at the end of the string, don't log as multi-line.
checker := &MyChecker{result: false}
log := "(?s)helpers_test.go:[0-9]+:.*\nhelpers_test.go:[0-9]+:\n" +
" return c\\.Check\\(\"a b\\\\n\", checker, \"a\\\\nb\"\\)\n" +
"\\.+ myobtained string = \"a b\\\\n\"\n" +
"\\.+ myexpected string = \"\" \\+\n" +
"\\.+ \"a\\\\n\" \\+\n" +
"\\.+ \"b\"\n\n"
testHelperFailure(c, `Check("a b\n", chk, "a\nb")`, false, false, log,
func() interface{} {
return c.Check("a b\n", checker, "a\nb")
})
}
// -----------------------------------------------------------------------
// MakeDir() tests.
type MkDirHelper struct {
path1 string
path2 string
isDir1 bool
isDir2 bool
isDir3 bool
isDir4 bool
}
func (s *MkDirHelper) SetUpSuite(c *check.C) {
s.path1 = c.MkDir()
s.isDir1 = isDir(s.path1)
}
func (s *MkDirHelper) Test(c *check.C) {
s.path2 = c.MkDir()
s.isDir2 = isDir(s.path2)
}
func (s *MkDirHelper) TearDownSuite(c *check.C) {
s.isDir3 = isDir(s.path1)
s.isDir4 = isDir(s.path2)
}
func (s *HelpersS) TestMkDir(c *check.C) {
helper := MkDirHelper{}
output := String{}
check.Run(&helper, &check.RunConf{Output: &output})
c.Assert(output.value, check.Equals, "")
c.Check(helper.isDir1, check.Equals, true)
c.Check(helper.isDir2, check.Equals, true)
c.Check(helper.isDir3, check.Equals, true)
c.Check(helper.isDir4, check.Equals, true)
c.Check(helper.path1, check.Not(check.Equals),
helper.path2)
c.Check(isDir(helper.path1), check.Equals, false)
c.Check(isDir(helper.path2), check.Equals, false)
}
func isDir(path string) bool {
if stat, err := os.Stat(path); err == nil {
return stat.IsDir()
}
return false
}
// Concurrent logging should not corrupt the underling buffer.
// Use go test -race to detect the race in this test.
func (s *HelpersS) TestConcurrentLogging(c *check.C) {
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(runtime.NumCPU()))
var start, stop sync.WaitGroup
start.Add(1)
for i, n := 0, runtime.NumCPU()*2; i < n; i++ {
stop.Add(1)
go func(i int) {
start.Wait()
for j := 0; j < 30; j++ {
c.Logf("Worker %d: line %d", i, j)
}
stop.Done()
}(i)
}
start.Done()
stop.Wait()
}
// -----------------------------------------------------------------------
// Test the TestName function
type TestNameHelper struct {
name1 string
name2 string
name3 string
name4 string
name5 string
}
func (s *TestNameHelper) SetUpSuite(c *check.C) { s.name1 = c.TestName() }
func (s *TestNameHelper) SetUpTest(c *check.C) { s.name2 = c.TestName() }
func (s *TestNameHelper) Test(c *check.C) { s.name3 = c.TestName() }
func (s *TestNameHelper) TearDownTest(c *check.C) { s.name4 = c.TestName() }
func (s *TestNameHelper) TearDownSuite(c *check.C) { s.name5 = c.TestName() }
func (s *HelpersS) TestTestName(c *check.C) {
helper := TestNameHelper{}
output := String{}
check.Run(&helper, &check.RunConf{Output: &output})
c.Check(helper.name1, check.Equals, "")
c.Check(helper.name2, check.Equals, "TestNameHelper.Test")
c.Check(helper.name3, check.Equals, "TestNameHelper.Test")
c.Check(helper.name4, check.Equals, "TestNameHelper.Test")
c.Check(helper.name5, check.Equals, "")
}
// -----------------------------------------------------------------------
// A couple of helper functions to test helper functions. :-)
func testHelperSuccess(c *check.C, name string, expectedResult interface{}, closure func() interface{}) {
var result interface{}
defer (func() {
if err := recover(); err != nil {
panic(err)
}
checkState(c, result,
&expectedState{
name: name,
result: expectedResult,
failed: false,
log: "",
})
})()
result = closure()
}
func testHelperFailure(c *check.C, name string, expectedResult interface{}, shouldStop bool, log string, closure func() interface{}) {
var result interface{}
defer (func() {
if err := recover(); err != nil {
panic(err)
}
checkState(c, result,
&expectedState{
name: name,
result: expectedResult,
failed: true,
log: log,
})
})()
result = closure()
if shouldStop {
c.Logf("%s didn't stop when it should", name)
}
}

View File

@ -1,94 +0,0 @@
// Integration tests
package check_test
import (
. "gitee.com/johng/gf/third/gopkg.in/check.v1"
)
// -----------------------------------------------------------------------
// Integration test suite.
type integrationS struct{}
var _ = Suite(&integrationS{})
type integrationTestHelper struct{}
func (s *integrationTestHelper) TestMultiLineStringEqualFails(c *C) {
c.Check("foo\nbar\nbaz\nboom\n", Equals, "foo\nbaar\nbaz\nboom\n")
}
func (s *integrationTestHelper) TestStringEqualFails(c *C) {
c.Check("foo", Equals, "bar")
}
func (s *integrationTestHelper) TestIntEqualFails(c *C) {
c.Check(42, Equals, 43)
}
type complexStruct struct {
r, i int
}
func (s *integrationTestHelper) TestStructEqualFails(c *C) {
c.Check(complexStruct{1, 2}, Equals, complexStruct{3, 4})
}
func (s *integrationS) TestOutput(c *C) {
helper := integrationTestHelper{}
output := String{}
Run(&helper, &RunConf{Output: &output})
c.Assert(output.value, Equals, `
----------------------------------------------------------------------
FAIL: integration_test.go:26: integrationTestHelper.TestIntEqualFails
integration_test.go:27:
c.Check(42, Equals, 43)
... obtained int = 42
... expected int = 43
----------------------------------------------------------------------
FAIL: integration_test.go:18: integrationTestHelper.TestMultiLineStringEqualFails
integration_test.go:19:
c.Check("foo\nbar\nbaz\nboom\n", Equals, "foo\nbaar\nbaz\nboom\n")
... obtained string = "" +
... "foo\n" +
... "bar\n" +
... "baz\n" +
... "boom\n"
... expected string = "" +
... "foo\n" +
... "baar\n" +
... "baz\n" +
... "boom\n"
... String difference:
... [1]: "bar" != "baar"
----------------------------------------------------------------------
FAIL: integration_test.go:22: integrationTestHelper.TestStringEqualFails
integration_test.go:23:
c.Check("foo", Equals, "bar")
... obtained string = "foo"
... expected string = "bar"
----------------------------------------------------------------------
FAIL: integration_test.go:34: integrationTestHelper.TestStructEqualFails
integration_test.go:35:
c.Check(complexStruct{1, 2}, Equals, complexStruct{3, 4})
... obtained check_test.complexStruct = check_test.complexStruct{r:1, i:2}
... expected check_test.complexStruct = check_test.complexStruct{r:3, i:4}
... Difference:
... r: 1 != 3
... i: 2 != 4
`)
}

View File

@ -1,168 +0,0 @@
package check
import (
"bytes"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"os"
)
func indent(s, with string) (r string) {
eol := true
for i := 0; i != len(s); i++ {
c := s[i]
switch {
case eol && c == '\n' || c == '\r':
case c == '\n' || c == '\r':
eol = true
case eol:
eol = false
s = s[:i] + with + s[i:]
i += len(with)
}
}
return s
}
func printLine(filename string, line int) (string, error) {
fset := token.NewFileSet()
file, err := os.Open(filename)
if err != nil {
return "", err
}
fnode, err := parser.ParseFile(fset, filename, file, parser.ParseComments)
if err != nil {
return "", err
}
config := &printer.Config{Mode: printer.UseSpaces, Tabwidth: 4}
lp := &linePrinter{fset: fset, fnode: fnode, line: line, config: config}
ast.Walk(lp, fnode)
result := lp.output.Bytes()
// Comments leave \n at the end.
n := len(result)
for n > 0 && result[n-1] == '\n' {
n--
}
return string(result[:n]), nil
}
type linePrinter struct {
config *printer.Config
fset *token.FileSet
fnode *ast.File
line int
output bytes.Buffer
stmt ast.Stmt
}
func (lp *linePrinter) emit() bool {
if lp.stmt != nil {
lp.trim(lp.stmt)
lp.printWithComments(lp.stmt)
lp.stmt = nil
return true
}
return false
}
func (lp *linePrinter) printWithComments(n ast.Node) {
nfirst := lp.fset.Position(n.Pos()).Line
nlast := lp.fset.Position(n.End()).Line
for _, g := range lp.fnode.Comments {
cfirst := lp.fset.Position(g.Pos()).Line
clast := lp.fset.Position(g.End()).Line
if clast == nfirst-1 && lp.fset.Position(n.Pos()).Column == lp.fset.Position(g.Pos()).Column {
for _, c := range g.List {
lp.output.WriteString(c.Text)
lp.output.WriteByte('\n')
}
}
if cfirst >= nfirst && cfirst <= nlast && n.End() <= g.List[0].Slash {
// The printer will not include the comment if it starts past
// the node itself. Trick it into printing by overlapping the
// slash with the end of the statement.
g.List[0].Slash = n.End() - 1
}
}
node := &printer.CommentedNode{n, lp.fnode.Comments}
lp.config.Fprint(&lp.output, lp.fset, node)
}
func (lp *linePrinter) Visit(n ast.Node) (w ast.Visitor) {
if n == nil {
if lp.output.Len() == 0 {
lp.emit()
}
return nil
}
first := lp.fset.Position(n.Pos()).Line
last := lp.fset.Position(n.End()).Line
if first <= lp.line && last >= lp.line {
// Print the innermost statement containing the line.
if stmt, ok := n.(ast.Stmt); ok {
if _, ok := n.(*ast.BlockStmt); !ok {
lp.stmt = stmt
}
}
if first == lp.line && lp.emit() {
return nil
}
return lp
}
return nil
}
func (lp *linePrinter) trim(n ast.Node) bool {
stmt, ok := n.(ast.Stmt)
if !ok {
return true
}
line := lp.fset.Position(n.Pos()).Line
if line != lp.line {
return false
}
switch stmt := stmt.(type) {
case *ast.IfStmt:
stmt.Body = lp.trimBlock(stmt.Body)
case *ast.SwitchStmt:
stmt.Body = lp.trimBlock(stmt.Body)
case *ast.TypeSwitchStmt:
stmt.Body = lp.trimBlock(stmt.Body)
case *ast.CaseClause:
stmt.Body = lp.trimList(stmt.Body)
case *ast.CommClause:
stmt.Body = lp.trimList(stmt.Body)
case *ast.BlockStmt:
stmt.List = lp.trimList(stmt.List)
}
return true
}
func (lp *linePrinter) trimBlock(stmt *ast.BlockStmt) *ast.BlockStmt {
if !lp.trim(stmt) {
return lp.emptyBlock(stmt)
}
stmt.Rbrace = stmt.Lbrace
return stmt
}
func (lp *linePrinter) trimList(stmts []ast.Stmt) []ast.Stmt {
for i := 0; i != len(stmts); i++ {
if !lp.trim(stmts[i]) {
stmts[i] = lp.emptyStmt(stmts[i])
break
}
}
return stmts
}
func (lp *linePrinter) emptyStmt(n ast.Node) *ast.ExprStmt {
return &ast.ExprStmt{&ast.Ellipsis{n.Pos(), nil}}
}
func (lp *linePrinter) emptyBlock(n ast.Node) *ast.BlockStmt {
p := n.Pos()
return &ast.BlockStmt{p, []ast.Stmt{lp.emptyStmt(n)}, p}
}

View File

@ -1,104 +0,0 @@
package check_test
import (
. "gitee.com/johng/gf/third/gopkg.in/check.v1"
)
var _ = Suite(&PrinterS{})
type PrinterS struct{}
func (s *PrinterS) TestCountSuite(c *C) {
suitesRun += 1
}
var printTestFuncLine int
func init() {
printTestFuncLine = getMyLine() + 3
}
func printTestFunc() {
println(1) // Comment1
if 2 == 2 { // Comment2
println(3) // Comment3
}
switch 5 {
case 6: println(6) // Comment6
println(7)
}
switch interface{}(9).(type) {// Comment9
case int: println(10)
println(11)
}
select {
case <-(chan bool)(nil): println(14)
println(15)
default: println(16)
println(17)
}
println(19,
20)
_ = func() { println(21)
println(22)
}
println(24, func() {
println(25)
})
// Leading comment
// with multiple lines.
println(29) // Comment29
}
var printLineTests = []struct {
line int
output string
}{
{1, "println(1) // Comment1"},
{2, "if 2 == 2 { // Comment2\n ...\n}"},
{3, "println(3) // Comment3"},
{5, "switch 5 {\n...\n}"},
{6, "case 6:\n println(6) // Comment6\n ..."},
{7, "println(7)"},
{9, "switch interface{}(9).(type) { // Comment9\n...\n}"},
{10, "case int:\n println(10)\n ..."},
{14, "case <-(chan bool)(nil):\n println(14)\n ..."},
{15, "println(15)"},
{16, "default:\n println(16)\n ..."},
{17, "println(17)"},
{19, "println(19,\n 20)"},
{20, "println(19,\n 20)"},
{21, "_ = func() {\n println(21)\n println(22)\n}"},
{22, "println(22)"},
{24, "println(24, func() {\n println(25)\n})"},
{25, "println(25)"},
{26, "println(24, func() {\n println(25)\n})"},
{29, "// Leading comment\n// with multiple lines.\nprintln(29) // Comment29"},
}
func (s *PrinterS) TestPrintLine(c *C) {
for _, test := range printLineTests {
output, err := PrintLine("printer_test.go", printTestFuncLine+test.line)
c.Assert(err, IsNil)
c.Assert(output, Equals, test.output)
}
}
var indentTests = []struct {
in, out string
}{
{"", ""},
{"\n", "\n"},
{"a", ">>>a"},
{"a\n", ">>>a\n"},
{"a\nb", ">>>a\n>>>b"},
{" ", ">>> "},
}
func (s *PrinterS) TestIndent(c *C) {
for _, test := range indentTests {
out := Indent(test.in, ">>>")
c.Assert(out, Equals, test.out)
}
}

View File

@ -1,88 +0,0 @@
package check
import (
"fmt"
"io"
"sync"
)
// -----------------------------------------------------------------------
// Output writer manages atomic output writing according to settings.
type outputWriter struct {
m sync.Mutex
writer io.Writer
wroteCallProblemLast bool
Stream bool
Verbose bool
}
func newOutputWriter(writer io.Writer, stream, verbose bool) *outputWriter {
return &outputWriter{writer: writer, Stream: stream, Verbose: verbose}
}
func (ow *outputWriter) Write(content []byte) (n int, err error) {
ow.m.Lock()
n, err = ow.writer.Write(content)
ow.m.Unlock()
return
}
func (ow *outputWriter) WriteCallStarted(label string, c *C) {
if ow.Stream {
header := renderCallHeader(label, c, "", "\n")
ow.m.Lock()
ow.writer.Write([]byte(header))
ow.m.Unlock()
}
}
func (ow *outputWriter) WriteCallProblem(label string, c *C) {
var prefix string
if !ow.Stream {
prefix = "\n-----------------------------------" +
"-----------------------------------\n"
}
header := renderCallHeader(label, c, prefix, "\n\n")
ow.m.Lock()
ow.wroteCallProblemLast = true
ow.writer.Write([]byte(header))
if !ow.Stream {
c.logb.WriteTo(ow.writer)
}
ow.m.Unlock()
}
func (ow *outputWriter) WriteCallSuccess(label string, c *C) {
if ow.Stream || (ow.Verbose && c.kind == testKd) {
// TODO Use a buffer here.
var suffix string
if c.reason != "" {
suffix = " (" + c.reason + ")"
}
if c.status() == succeededSt {
suffix += "\t" + c.timerString()
}
suffix += "\n"
if ow.Stream {
suffix += "\n"
}
header := renderCallHeader(label, c, "", suffix)
ow.m.Lock()
// Resist temptation of using line as prefix above due to race.
if !ow.Stream && ow.wroteCallProblemLast {
header = "\n-----------------------------------" +
"-----------------------------------\n" +
header
}
ow.wroteCallProblemLast = false
ow.writer.Write([]byte(header))
ow.m.Unlock()
}
}
func renderCallHeader(label string, c *C, prefix, suffix string) string {
pc := c.method.PC()
return fmt.Sprintf("%s%s: %s: %s%s", prefix, label, niceFuncPath(pc),
niceFuncName(pc), suffix)
}

View File

@ -1,159 +0,0 @@
package check_test
import (
"fmt"
"path/filepath"
"runtime"
. "gitee.com/johng/gf/third/gopkg.in/check.v1"
)
var _ = Suite(&reporterS{})
type reporterS struct {
testFile string
}
func (s *reporterS) SetUpSuite(c *C) {
_, fileName, _, ok := runtime.Caller(0)
c.Assert(ok, Equals, true)
s.testFile = filepath.Base(fileName)
}
func (s *reporterS) TestWrite(c *C) {
testString := "test string"
output := String{}
dummyStream := true
dummyVerbose := true
o := NewOutputWriter(&output, dummyStream, dummyVerbose)
o.Write([]byte(testString))
c.Assert(output.value, Equals, testString)
}
func (s *reporterS) TestWriteCallStartedWithStreamFlag(c *C) {
testLabel := "test started label"
stream := true
output := String{}
dummyVerbose := true
o := NewOutputWriter(&output, stream, dummyVerbose)
o.WriteCallStarted(testLabel, c)
expected := fmt.Sprintf("%s: %s:\\d+: %s\n", testLabel, s.testFile, c.TestName())
c.Assert(output.value, Matches, expected)
}
func (s *reporterS) TestWriteCallStartedWithoutStreamFlag(c *C) {
stream := false
output := String{}
dummyLabel := "dummy"
dummyVerbose := true
o := NewOutputWriter(&output, stream, dummyVerbose)
o.WriteCallStarted(dummyLabel, c)
c.Assert(output.value, Equals, "")
}
func (s *reporterS) TestWriteCallProblemWithStreamFlag(c *C) {
testLabel := "test problem label"
stream := true
output := String{}
dummyVerbose := true
o := NewOutputWriter(&output, stream, dummyVerbose)
o.WriteCallProblem(testLabel, c)
expected := fmt.Sprintf("%s: %s:\\d+: %s\n\n", testLabel, s.testFile, c.TestName())
c.Assert(output.value, Matches, expected)
}
func (s *reporterS) TestWriteCallProblemWithoutStreamFlag(c *C) {
testLabel := "test problem label"
stream := false
output := String{}
dummyVerbose := true
o := NewOutputWriter(&output, stream, dummyVerbose)
o.WriteCallProblem(testLabel, c)
expected := fmt.Sprintf(""+
"\n"+
"----------------------------------------------------------------------\n"+
"%s: %s:\\d+: %s\n\n", testLabel, s.testFile, c.TestName())
c.Assert(output.value, Matches, expected)
}
func (s *reporterS) TestWriteCallProblemWithoutStreamFlagWithLog(c *C) {
testLabel := "test problem label"
testLog := "test log"
stream := false
output := String{}
dummyVerbose := true
o := NewOutputWriter(&output, stream, dummyVerbose)
c.Log(testLog)
o.WriteCallProblem(testLabel, c)
expected := fmt.Sprintf(""+
"\n"+
"----------------------------------------------------------------------\n"+
"%s: %s:\\d+: %s\n\n%s\n", testLabel, s.testFile, c.TestName(), testLog)
c.Assert(output.value, Matches, expected)
}
func (s *reporterS) TestWriteCallSuccessWithStreamFlag(c *C) {
testLabel := "test success label"
stream := true
output := String{}
dummyVerbose := true
o := NewOutputWriter(&output, stream, dummyVerbose)
o.WriteCallSuccess(testLabel, c)
expected := fmt.Sprintf("%s: %s:\\d+: %s\t\\d\\.\\d+s\n\n", testLabel, s.testFile, c.TestName())
c.Assert(output.value, Matches, expected)
}
func (s *reporterS) TestWriteCallSuccessWithStreamFlagAndReason(c *C) {
testLabel := "test success label"
testReason := "test skip reason"
stream := true
output := String{}
dummyVerbose := true
o := NewOutputWriter(&output, stream, dummyVerbose)
c.FakeSkip(testReason)
o.WriteCallSuccess(testLabel, c)
expected := fmt.Sprintf("%s: %s:\\d+: %s \\(%s\\)\t\\d\\.\\d+s\n\n",
testLabel, s.testFile, c.TestName(), testReason)
c.Assert(output.value, Matches, expected)
}
func (s *reporterS) TestWriteCallSuccessWithoutStreamFlagWithVerboseFlag(c *C) {
testLabel := "test success label"
stream := false
verbose := true
output := String{}
o := NewOutputWriter(&output, stream, verbose)
o.WriteCallSuccess(testLabel, c)
expected := fmt.Sprintf("%s: %s:\\d+: %s\t\\d\\.\\d+s\n", testLabel, s.testFile, c.TestName())
c.Assert(output.value, Matches, expected)
}
func (s *reporterS) TestWriteCallSuccessWithoutStreamFlagWithoutVerboseFlag(c *C) {
testLabel := "test success label"
stream := false
verbose := false
output := String{}
o := NewOutputWriter(&output, stream, verbose)
o.WriteCallSuccess(testLabel, c)
c.Assert(output.value, Equals, "")
}

View File

@ -1,175 +0,0 @@
package check
import (
"bufio"
"flag"
"fmt"
"os"
"testing"
"time"
)
// -----------------------------------------------------------------------
// Test suite registry.
var allSuites []interface{}
// Suite registers the given value as a test suite to be run. Any methods
// starting with the Test prefix in the given value will be considered as
// a test method.
func Suite(suite interface{}) interface{} {
allSuites = append(allSuites, suite)
return suite
}
// -----------------------------------------------------------------------
// Public running interface.
var (
oldFilterFlag = flag.String("gocheck.f", "", "Regular expression selecting which tests and/or suites to run")
oldVerboseFlag = flag.Bool("gocheck.v", false, "Verbose mode")
oldStreamFlag = flag.Bool("gocheck.vv", false, "Super verbose mode (disables output caching)")
oldBenchFlag = flag.Bool("gocheck.b", false, "Run benchmarks")
oldBenchTime = flag.Duration("gocheck.btime", 1*time.Second, "approximate run time for each benchmark")
oldListFlag = flag.Bool("gocheck.list", false, "List the names of all tests that will be run")
oldWorkFlag = flag.Bool("gocheck.work", false, "Display and do not remove the test working directory")
newFilterFlag = flag.String("check.f", "", "Regular expression selecting which tests and/or suites to run")
newVerboseFlag = flag.Bool("check.v", false, "Verbose mode")
newStreamFlag = flag.Bool("check.vv", false, "Super verbose mode (disables output caching)")
newBenchFlag = flag.Bool("check.b", false, "Run benchmarks")
newBenchTime = flag.Duration("check.btime", 1*time.Second, "approximate run time for each benchmark")
newBenchMem = flag.Bool("check.bmem", false, "Report memory benchmarks")
newListFlag = flag.Bool("check.list", false, "List the names of all tests that will be run")
newWorkFlag = flag.Bool("check.work", false, "Display and do not remove the test working directory")
)
// TestingT runs all test suites registered with the Suite function,
// printing results to stdout, and reporting any failures back to
// the "testing" package.
func TestingT(testingT *testing.T) {
benchTime := *newBenchTime
if benchTime == 1*time.Second {
benchTime = *oldBenchTime
}
conf := &RunConf{
Filter: *oldFilterFlag + *newFilterFlag,
Verbose: *oldVerboseFlag || *newVerboseFlag,
Stream: *oldStreamFlag || *newStreamFlag,
Benchmark: *oldBenchFlag || *newBenchFlag,
BenchmarkTime: benchTime,
BenchmarkMem: *newBenchMem,
KeepWorkDir: *oldWorkFlag || *newWorkFlag,
}
if *oldListFlag || *newListFlag {
w := bufio.NewWriter(os.Stdout)
for _, name := range ListAll(conf) {
fmt.Fprintln(w, name)
}
w.Flush()
return
}
result := RunAll(conf)
println(result.String())
if !result.Passed() {
testingT.Fail()
}
}
// RunAll runs all test suites registered with the Suite function, using the
// provided run configuration.
func RunAll(runConf *RunConf) *Result {
result := Result{}
for _, suite := range allSuites {
result.Add(Run(suite, runConf))
}
return &result
}
// Run runs the provided test suite using the provided run configuration.
func Run(suite interface{}, runConf *RunConf) *Result {
runner := newSuiteRunner(suite, runConf)
return runner.run()
}
// ListAll returns the names of all the test functions registered with the
// Suite function that will be run with the provided run configuration.
func ListAll(runConf *RunConf) []string {
var names []string
for _, suite := range allSuites {
names = append(names, List(suite, runConf)...)
}
return names
}
// List returns the names of the test functions in the given
// suite that will be run with the provided run configuration.
func List(suite interface{}, runConf *RunConf) []string {
var names []string
runner := newSuiteRunner(suite, runConf)
for _, t := range runner.tests {
names = append(names, t.String())
}
return names
}
// -----------------------------------------------------------------------
// Result methods.
func (r *Result) Add(other *Result) {
r.Succeeded += other.Succeeded
r.Skipped += other.Skipped
r.Failed += other.Failed
r.Panicked += other.Panicked
r.FixturePanicked += other.FixturePanicked
r.ExpectedFailures += other.ExpectedFailures
r.Missed += other.Missed
if r.WorkDir != "" && other.WorkDir != "" {
r.WorkDir += ":" + other.WorkDir
} else if other.WorkDir != "" {
r.WorkDir = other.WorkDir
}
}
func (r *Result) Passed() bool {
return (r.Failed == 0 && r.Panicked == 0 &&
r.FixturePanicked == 0 && r.Missed == 0 &&
r.RunError == nil)
}
func (r *Result) String() string {
if r.RunError != nil {
return "ERROR: " + r.RunError.Error()
}
var value string
if r.Failed == 0 && r.Panicked == 0 && r.FixturePanicked == 0 &&
r.Missed == 0 {
value = "OK: "
} else {
value = "OOPS: "
}
value += fmt.Sprintf("%d passed", r.Succeeded)
if r.Skipped != 0 {
value += fmt.Sprintf(", %d skipped", r.Skipped)
}
if r.ExpectedFailures != 0 {
value += fmt.Sprintf(", %d expected failures", r.ExpectedFailures)
}
if r.Failed != 0 {
value += fmt.Sprintf(", %d FAILED", r.Failed)
}
if r.Panicked != 0 {
value += fmt.Sprintf(", %d PANICKED", r.Panicked)
}
if r.FixturePanicked != 0 {
value += fmt.Sprintf(", %d FIXTURE-PANICKED", r.FixturePanicked)
}
if r.Missed != 0 {
value += fmt.Sprintf(", %d MISSED", r.Missed)
}
if r.WorkDir != "" {
value += "\nWORK=" + r.WorkDir
}
return value
}

View File

@ -1,419 +0,0 @@
// These tests verify the test running logic.
package check_test
import (
"errors"
. "gitee.com/johng/gf/third/gopkg.in/check.v1"
"os"
"sync"
)
var runnerS = Suite(&RunS{})
type RunS struct{}
func (s *RunS) TestCountSuite(c *C) {
suitesRun += 1
}
// -----------------------------------------------------------------------
// Tests ensuring result counting works properly.
func (s *RunS) TestSuccess(c *C) {
output := String{}
result := Run(&SuccessHelper{}, &RunConf{Output: &output})
c.Check(result.Succeeded, Equals, 1)
c.Check(result.Failed, Equals, 0)
c.Check(result.Skipped, Equals, 0)
c.Check(result.Panicked, Equals, 0)
c.Check(result.FixturePanicked, Equals, 0)
c.Check(result.Missed, Equals, 0)
c.Check(result.RunError, IsNil)
}
func (s *RunS) TestFailure(c *C) {
output := String{}
result := Run(&FailHelper{}, &RunConf{Output: &output})
c.Check(result.Succeeded, Equals, 0)
c.Check(result.Failed, Equals, 1)
c.Check(result.Skipped, Equals, 0)
c.Check(result.Panicked, Equals, 0)
c.Check(result.FixturePanicked, Equals, 0)
c.Check(result.Missed, Equals, 0)
c.Check(result.RunError, IsNil)
}
func (s *RunS) TestFixture(c *C) {
output := String{}
result := Run(&FixtureHelper{}, &RunConf{Output: &output})
c.Check(result.Succeeded, Equals, 2)
c.Check(result.Failed, Equals, 0)
c.Check(result.Skipped, Equals, 0)
c.Check(result.Panicked, Equals, 0)
c.Check(result.FixturePanicked, Equals, 0)
c.Check(result.Missed, Equals, 0)
c.Check(result.RunError, IsNil)
}
func (s *RunS) TestPanicOnTest(c *C) {
output := String{}
helper := &FixtureHelper{panicOn: "Test1"}
result := Run(helper, &RunConf{Output: &output})
c.Check(result.Succeeded, Equals, 1)
c.Check(result.Failed, Equals, 0)
c.Check(result.Skipped, Equals, 0)
c.Check(result.Panicked, Equals, 1)
c.Check(result.FixturePanicked, Equals, 0)
c.Check(result.Missed, Equals, 0)
c.Check(result.RunError, IsNil)
}
func (s *RunS) TestPanicOnSetUpTest(c *C) {
output := String{}
helper := &FixtureHelper{panicOn: "SetUpTest"}
result := Run(helper, &RunConf{Output: &output})
c.Check(result.Succeeded, Equals, 0)
c.Check(result.Failed, Equals, 0)
c.Check(result.Skipped, Equals, 0)
c.Check(result.Panicked, Equals, 0)
c.Check(result.FixturePanicked, Equals, 1)
c.Check(result.Missed, Equals, 2)
c.Check(result.RunError, IsNil)
}
func (s *RunS) TestPanicOnSetUpSuite(c *C) {
output := String{}
helper := &FixtureHelper{panicOn: "SetUpSuite"}
result := Run(helper, &RunConf{Output: &output})
c.Check(result.Succeeded, Equals, 0)
c.Check(result.Failed, Equals, 0)
c.Check(result.Skipped, Equals, 0)
c.Check(result.Panicked, Equals, 0)
c.Check(result.FixturePanicked, Equals, 1)
c.Check(result.Missed, Equals, 2)
c.Check(result.RunError, IsNil)
}
// -----------------------------------------------------------------------
// Check result aggregation.
func (s *RunS) TestAdd(c *C) {
result := &Result{
Succeeded: 1,
Skipped: 2,
Failed: 3,
Panicked: 4,
FixturePanicked: 5,
Missed: 6,
ExpectedFailures: 7,
}
result.Add(&Result{
Succeeded: 10,
Skipped: 20,
Failed: 30,
Panicked: 40,
FixturePanicked: 50,
Missed: 60,
ExpectedFailures: 70,
})
c.Check(result.Succeeded, Equals, 11)
c.Check(result.Skipped, Equals, 22)
c.Check(result.Failed, Equals, 33)
c.Check(result.Panicked, Equals, 44)
c.Check(result.FixturePanicked, Equals, 55)
c.Check(result.Missed, Equals, 66)
c.Check(result.ExpectedFailures, Equals, 77)
c.Check(result.RunError, IsNil)
}
// -----------------------------------------------------------------------
// Check the Passed() method.
func (s *RunS) TestPassed(c *C) {
c.Assert((&Result{}).Passed(), Equals, true)
c.Assert((&Result{Succeeded: 1}).Passed(), Equals, true)
c.Assert((&Result{Skipped: 1}).Passed(), Equals, true)
c.Assert((&Result{Failed: 1}).Passed(), Equals, false)
c.Assert((&Result{Panicked: 1}).Passed(), Equals, false)
c.Assert((&Result{FixturePanicked: 1}).Passed(), Equals, false)
c.Assert((&Result{Missed: 1}).Passed(), Equals, false)
c.Assert((&Result{RunError: errors.New("!")}).Passed(), Equals, false)
}
// -----------------------------------------------------------------------
// Check that result printing is working correctly.
func (s *RunS) TestPrintSuccess(c *C) {
result := &Result{Succeeded: 5}
c.Check(result.String(), Equals, "OK: 5 passed")
}
func (s *RunS) TestPrintFailure(c *C) {
result := &Result{Failed: 5}
c.Check(result.String(), Equals, "OOPS: 0 passed, 5 FAILED")
}
func (s *RunS) TestPrintSkipped(c *C) {
result := &Result{Skipped: 5}
c.Check(result.String(), Equals, "OK: 0 passed, 5 skipped")
}
func (s *RunS) TestPrintExpectedFailures(c *C) {
result := &Result{ExpectedFailures: 5}
c.Check(result.String(), Equals, "OK: 0 passed, 5 expected failures")
}
func (s *RunS) TestPrintPanicked(c *C) {
result := &Result{Panicked: 5}
c.Check(result.String(), Equals, "OOPS: 0 passed, 5 PANICKED")
}
func (s *RunS) TestPrintFixturePanicked(c *C) {
result := &Result{FixturePanicked: 5}
c.Check(result.String(), Equals, "OOPS: 0 passed, 5 FIXTURE-PANICKED")
}
func (s *RunS) TestPrintMissed(c *C) {
result := &Result{Missed: 5}
c.Check(result.String(), Equals, "OOPS: 0 passed, 5 MISSED")
}
func (s *RunS) TestPrintAll(c *C) {
result := &Result{Succeeded: 1, Skipped: 2, ExpectedFailures: 3,
Panicked: 4, FixturePanicked: 5, Missed: 6}
c.Check(result.String(), Equals,
"OOPS: 1 passed, 2 skipped, 3 expected failures, 4 PANICKED, "+
"5 FIXTURE-PANICKED, 6 MISSED")
}
func (s *RunS) TestPrintRunError(c *C) {
result := &Result{Succeeded: 1, Failed: 1,
RunError: errors.New("Kaboom!")}
c.Check(result.String(), Equals, "ERROR: Kaboom!")
}
// -----------------------------------------------------------------------
// Verify that the method pattern flag works correctly.
func (s *RunS) TestFilterTestName(c *C) {
helper := FixtureHelper{}
output := String{}
runConf := RunConf{Output: &output, Filter: "Test[91]"}
Run(&helper, &runConf)
c.Check(helper.calls[0], Equals, "SetUpSuite")
c.Check(helper.calls[1], Equals, "SetUpTest")
c.Check(helper.calls[2], Equals, "Test1")
c.Check(helper.calls[3], Equals, "TearDownTest")
c.Check(helper.calls[4], Equals, "TearDownSuite")
c.Check(len(helper.calls), Equals, 5)
}
func (s *RunS) TestFilterTestNameWithAll(c *C) {
helper := FixtureHelper{}
output := String{}
runConf := RunConf{Output: &output, Filter: ".*"}
Run(&helper, &runConf)
c.Check(helper.calls[0], Equals, "SetUpSuite")
c.Check(helper.calls[1], Equals, "SetUpTest")
c.Check(helper.calls[2], Equals, "Test1")
c.Check(helper.calls[3], Equals, "TearDownTest")
c.Check(helper.calls[4], Equals, "SetUpTest")
c.Check(helper.calls[5], Equals, "Test2")
c.Check(helper.calls[6], Equals, "TearDownTest")
c.Check(helper.calls[7], Equals, "TearDownSuite")
c.Check(len(helper.calls), Equals, 8)
}
func (s *RunS) TestFilterSuiteName(c *C) {
helper := FixtureHelper{}
output := String{}
runConf := RunConf{Output: &output, Filter: "FixtureHelper"}
Run(&helper, &runConf)
c.Check(helper.calls[0], Equals, "SetUpSuite")
c.Check(helper.calls[1], Equals, "SetUpTest")
c.Check(helper.calls[2], Equals, "Test1")
c.Check(helper.calls[3], Equals, "TearDownTest")
c.Check(helper.calls[4], Equals, "SetUpTest")
c.Check(helper.calls[5], Equals, "Test2")
c.Check(helper.calls[6], Equals, "TearDownTest")
c.Check(helper.calls[7], Equals, "TearDownSuite")
c.Check(len(helper.calls), Equals, 8)
}
func (s *RunS) TestFilterSuiteNameAndTestName(c *C) {
helper := FixtureHelper{}
output := String{}
runConf := RunConf{Output: &output, Filter: "FixtureHelper\\.Test2"}
Run(&helper, &runConf)
c.Check(helper.calls[0], Equals, "SetUpSuite")
c.Check(helper.calls[1], Equals, "SetUpTest")
c.Check(helper.calls[2], Equals, "Test2")
c.Check(helper.calls[3], Equals, "TearDownTest")
c.Check(helper.calls[4], Equals, "TearDownSuite")
c.Check(len(helper.calls), Equals, 5)
}
func (s *RunS) TestFilterAllOut(c *C) {
helper := FixtureHelper{}
output := String{}
runConf := RunConf{Output: &output, Filter: "NotFound"}
Run(&helper, &runConf)
c.Check(len(helper.calls), Equals, 0)
}
func (s *RunS) TestRequirePartialMatch(c *C) {
helper := FixtureHelper{}
output := String{}
runConf := RunConf{Output: &output, Filter: "est"}
Run(&helper, &runConf)
c.Check(len(helper.calls), Equals, 8)
}
func (s *RunS) TestFilterError(c *C) {
helper := FixtureHelper{}
output := String{}
runConf := RunConf{Output: &output, Filter: "]["}
result := Run(&helper, &runConf)
c.Check(result.String(), Equals,
"ERROR: Bad filter expression: error parsing regexp: missing closing ]: `[`")
c.Check(len(helper.calls), Equals, 0)
}
// -----------------------------------------------------------------------
// Verify that List works correctly.
func (s *RunS) TestListFiltered(c *C) {
names := List(&FixtureHelper{}, &RunConf{Filter: "1"})
c.Assert(names, DeepEquals, []string{
"FixtureHelper.Test1",
})
}
func (s *RunS) TestList(c *C) {
names := List(&FixtureHelper{}, &RunConf{})
c.Assert(names, DeepEquals, []string{
"FixtureHelper.Test1",
"FixtureHelper.Test2",
})
}
// -----------------------------------------------------------------------
// Verify that verbose mode prints tests which pass as well.
func (s *RunS) TestVerboseMode(c *C) {
helper := FixtureHelper{}
output := String{}
runConf := RunConf{Output: &output, Verbose: true}
Run(&helper, &runConf)
expected := "PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Test1\t *[.0-9]+s\n" +
"PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Test2\t *[.0-9]+s\n"
c.Assert(output.value, Matches, expected)
}
func (s *RunS) TestVerboseModeWithFailBeforePass(c *C) {
helper := FixtureHelper{panicOn: "Test1"}
output := String{}
runConf := RunConf{Output: &output, Verbose: true}
Run(&helper, &runConf)
expected := "(?s).*PANIC.*\n-+\n" + // Should have an extra line.
"PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Test2\t *[.0-9]+s\n"
c.Assert(output.value, Matches, expected)
}
// -----------------------------------------------------------------------
// Verify the stream output mode. In this mode there's no output caching.
type StreamHelper struct {
l2 sync.Mutex
l3 sync.Mutex
}
func (s *StreamHelper) SetUpSuite(c *C) {
c.Log("0")
}
func (s *StreamHelper) Test1(c *C) {
c.Log("1")
s.l2.Lock()
s.l3.Lock()
go func() {
s.l2.Lock() // Wait for "2".
c.Log("3")
s.l3.Unlock()
}()
}
func (s *StreamHelper) Test2(c *C) {
c.Log("2")
s.l2.Unlock()
s.l3.Lock() // Wait for "3".
c.Fail()
c.Log("4")
}
func (s *RunS) TestStreamMode(c *C) {
helper := &StreamHelper{}
output := String{}
runConf := RunConf{Output: &output, Stream: true}
Run(helper, &runConf)
expected := "START: run_test\\.go:[0-9]+: StreamHelper\\.SetUpSuite\n0\n" +
"PASS: run_test\\.go:[0-9]+: StreamHelper\\.SetUpSuite\t *[.0-9]+s\n\n" +
"START: run_test\\.go:[0-9]+: StreamHelper\\.Test1\n1\n" +
"PASS: run_test\\.go:[0-9]+: StreamHelper\\.Test1\t *[.0-9]+s\n\n" +
"START: run_test\\.go:[0-9]+: StreamHelper\\.Test2\n2\n3\n4\n" +
"FAIL: run_test\\.go:[0-9]+: StreamHelper\\.Test2\n\n"
c.Assert(output.value, Matches, expected)
}
type StreamMissHelper struct{}
func (s *StreamMissHelper) SetUpSuite(c *C) {
c.Log("0")
c.Fail()
}
func (s *StreamMissHelper) Test1(c *C) {
c.Log("1")
}
func (s *RunS) TestStreamModeWithMiss(c *C) {
helper := &StreamMissHelper{}
output := String{}
runConf := RunConf{Output: &output, Stream: true}
Run(helper, &runConf)
expected := "START: run_test\\.go:[0-9]+: StreamMissHelper\\.SetUpSuite\n0\n" +
"FAIL: run_test\\.go:[0-9]+: StreamMissHelper\\.SetUpSuite\n\n" +
"START: run_test\\.go:[0-9]+: StreamMissHelper\\.Test1\n" +
"MISS: run_test\\.go:[0-9]+: StreamMissHelper\\.Test1\n\n"
c.Assert(output.value, Matches, expected)
}
// -----------------------------------------------------------------------
// Verify that that the keep work dir request indeed does so.
type WorkDirSuite struct {}
func (s *WorkDirSuite) Test(c *C) {
c.MkDir()
}
func (s *RunS) TestKeepWorkDir(c *C) {
output := String{}
runConf := RunConf{Output: &output, Verbose: true, KeepWorkDir: true}
result := Run(&WorkDirSuite{}, &runConf)
c.Assert(result.String(), Matches, ".*\nWORK=" + result.WorkDir)
stat, err := os.Stat(result.WorkDir)
c.Assert(err, IsNil)
c.Assert(stat.IsDir(), Equals, true)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,501 +0,0 @@
package yaml_test
import (
"fmt"
"math"
"strconv"
"strings"
"time"
. "gitee.com/johng/gf/third/gopkg.in/check.v1"
"gitee.com/johng/gf/third/gopkg.in/yaml.v2"
"net"
"os"
)
var marshalIntTest = 123
var marshalTests = []struct {
value interface{}
data string
}{
{
nil,
"null\n",
}, {
&struct{}{},
"{}\n",
}, {
map[string]string{"v": "hi"},
"v: hi\n",
}, {
map[string]interface{}{"v": "hi"},
"v: hi\n",
}, {
map[string]string{"v": "true"},
"v: \"true\"\n",
}, {
map[string]string{"v": "false"},
"v: \"false\"\n",
}, {
map[string]interface{}{"v": true},
"v: true\n",
}, {
map[string]interface{}{"v": false},
"v: false\n",
}, {
map[string]interface{}{"v": 10},
"v: 10\n",
}, {
map[string]interface{}{"v": -10},
"v: -10\n",
}, {
map[string]uint{"v": 42},
"v: 42\n",
}, {
map[string]interface{}{"v": int64(4294967296)},
"v: 4294967296\n",
}, {
map[string]int64{"v": int64(4294967296)},
"v: 4294967296\n",
}, {
map[string]uint64{"v": 4294967296},
"v: 4294967296\n",
}, {
map[string]interface{}{"v": "10"},
"v: \"10\"\n",
}, {
map[string]interface{}{"v": 0.1},
"v: 0.1\n",
}, {
map[string]interface{}{"v": float64(0.1)},
"v: 0.1\n",
}, {
map[string]interface{}{"v": -0.1},
"v: -0.1\n",
}, {
map[string]interface{}{"v": math.Inf(+1)},
"v: .inf\n",
}, {
map[string]interface{}{"v": math.Inf(-1)},
"v: -.inf\n",
}, {
map[string]interface{}{"v": math.NaN()},
"v: .nan\n",
}, {
map[string]interface{}{"v": nil},
"v: null\n",
}, {
map[string]interface{}{"v": ""},
"v: \"\"\n",
}, {
map[string][]string{"v": []string{"A", "B"}},
"v:\n- A\n- B\n",
}, {
map[string][]string{"v": []string{"A", "B\nC"}},
"v:\n- A\n- |-\n B\n C\n",
}, {
map[string][]interface{}{"v": []interface{}{"A", 1, map[string][]int{"B": []int{2, 3}}}},
"v:\n- A\n- 1\n- B:\n - 2\n - 3\n",
}, {
map[string]interface{}{"a": map[interface{}]interface{}{"b": "c"}},
"a:\n b: c\n",
}, {
map[string]interface{}{"a": "-"},
"a: '-'\n",
},
// Simple values.
{
&marshalIntTest,
"123\n",
},
// Structures
{
&struct{ Hello string }{"world"},
"hello: world\n",
}, {
&struct {
A struct {
B string
}
}{struct{ B string }{"c"}},
"a:\n b: c\n",
}, {
&struct {
A *struct {
B string
}
}{&struct{ B string }{"c"}},
"a:\n b: c\n",
}, {
&struct {
A *struct {
B string
}
}{},
"a: null\n",
}, {
&struct{ A int }{1},
"a: 1\n",
}, {
&struct{ A []int }{[]int{1, 2}},
"a:\n- 1\n- 2\n",
}, {
&struct {
B int "a"
}{1},
"a: 1\n",
}, {
&struct{ A bool }{true},
"a: true\n",
},
// Conditional flag
{
&struct {
A int "a,omitempty"
B int "b,omitempty"
}{1, 0},
"a: 1\n",
}, {
&struct {
A int "a,omitempty"
B int "b,omitempty"
}{0, 0},
"{}\n",
}, {
&struct {
A *struct{ X, y int } "a,omitempty,flow"
}{&struct{ X, y int }{1, 2}},
"a: {x: 1}\n",
}, {
&struct {
A *struct{ X, y int } "a,omitempty,flow"
}{nil},
"{}\n",
}, {
&struct {
A *struct{ X, y int } "a,omitempty,flow"
}{&struct{ X, y int }{}},
"a: {x: 0}\n",
}, {
&struct {
A struct{ X, y int } "a,omitempty,flow"
}{struct{ X, y int }{1, 2}},
"a: {x: 1}\n",
}, {
&struct {
A struct{ X, y int } "a,omitempty,flow"
}{struct{ X, y int }{0, 1}},
"{}\n",
}, {
&struct {
A float64 "a,omitempty"
B float64 "b,omitempty"
}{1, 0},
"a: 1\n",
},
// Flow flag
{
&struct {
A []int "a,flow"
}{[]int{1, 2}},
"a: [1, 2]\n",
}, {
&struct {
A map[string]string "a,flow"
}{map[string]string{"b": "c", "d": "e"}},
"a: {b: c, d: e}\n",
}, {
&struct {
A struct {
B, D string
} "a,flow"
}{struct{ B, D string }{"c", "e"}},
"a: {b: c, d: e}\n",
},
// Unexported field
{
&struct {
u int
A int
}{0, 1},
"a: 1\n",
},
// Ignored field
{
&struct {
A int
B int "-"
}{1, 2},
"a: 1\n",
},
// Struct inlining
{
&struct {
A int
C inlineB `yaml:",inline"`
}{1, inlineB{2, inlineC{3}}},
"a: 1\nb: 2\nc: 3\n",
},
// Map inlining
{
&struct {
A int
C map[string]int `yaml:",inline"`
}{1, map[string]int{"b": 2, "c": 3}},
"a: 1\nb: 2\nc: 3\n",
},
// Duration
{
map[string]time.Duration{"a": 3 * time.Second},
"a: 3s\n",
},
// Issue #24: bug in map merging logic.
{
map[string]string{"a": "<foo>"},
"a: <foo>\n",
},
// Issue #34: marshal unsupported base 60 floats quoted for compatibility
// with old YAML 1.1 parsers.
{
map[string]string{"a": "1:1"},
"a: \"1:1\"\n",
},
// Binary data.
{
map[string]string{"a": "\x00"},
"a: \"\\0\"\n",
}, {
map[string]string{"a": "\x80\x81\x82"},
"a: !!binary gIGC\n",
}, {
map[string]string{"a": strings.Repeat("\x90", 54)},
"a: !!binary |\n " + strings.Repeat("kJCQ", 17) + "kJ\n CQ\n",
},
// Ordered maps.
{
&yaml.MapSlice{{"b", 2}, {"a", 1}, {"d", 4}, {"c", 3}, {"sub", yaml.MapSlice{{"e", 5}}}},
"b: 2\na: 1\nd: 4\nc: 3\nsub:\n e: 5\n",
},
// Encode unicode as utf-8 rather than in escaped form.
{
map[string]string{"a": "你好"},
"a: 你好\n",
},
// Support encoding.TextMarshaler.
{
map[string]net.IP{"a": net.IPv4(1, 2, 3, 4)},
"a: 1.2.3.4\n",
},
{
map[string]time.Time{"a": time.Unix(1424801979, 0)},
"a: 2015-02-24T18:19:39Z\n",
},
// Ensure strings containing ": " are quoted (reported as PR #43, but not reproducible).
{
map[string]string{"a": "b: c"},
"a: 'b: c'\n",
},
// Containing hash mark ('#') in string should be quoted
{
map[string]string{"a": "Hello #comment"},
"a: 'Hello #comment'\n",
},
{
map[string]string{"a": "你好 #comment"},
"a: '你好 #comment'\n",
},
}
func (s *S) TestMarshal(c *C) {
defer os.Setenv("TZ", os.Getenv("TZ"))
os.Setenv("TZ", "UTC")
for _, item := range marshalTests {
data, err := yaml.Marshal(item.value)
c.Assert(err, IsNil)
c.Assert(string(data), Equals, item.data)
}
}
var marshalErrorTests = []struct {
value interface{}
error string
panic string
}{{
value: &struct {
B int
inlineB ",inline"
}{1, inlineB{2, inlineC{3}}},
panic: `Duplicated key 'b' in struct struct \{ B int; .*`,
}, {
value: &struct {
A int
B map[string]int ",inline"
}{1, map[string]int{"a": 2}},
panic: `Can't have key "a" in inlined map; conflicts with struct field`,
}}
func (s *S) TestMarshalErrors(c *C) {
for _, item := range marshalErrorTests {
if item.panic != "" {
c.Assert(func() { yaml.Marshal(item.value) }, PanicMatches, item.panic)
} else {
_, err := yaml.Marshal(item.value)
c.Assert(err, ErrorMatches, item.error)
}
}
}
func (s *S) TestMarshalTypeCache(c *C) {
var data []byte
var err error
func() {
type T struct{ A int }
data, err = yaml.Marshal(&T{})
c.Assert(err, IsNil)
}()
func() {
type T struct{ B int }
data, err = yaml.Marshal(&T{})
c.Assert(err, IsNil)
}()
c.Assert(string(data), Equals, "b: 0\n")
}
var marshalerTests = []struct {
data string
value interface{}
}{
{"_:\n hi: there\n", map[interface{}]interface{}{"hi": "there"}},
{"_:\n- 1\n- A\n", []interface{}{1, "A"}},
{"_: 10\n", 10},
{"_: null\n", nil},
{"_: BAR!\n", "BAR!"},
}
type marshalerType struct {
value interface{}
}
func (o marshalerType) MarshalText() ([]byte, error) {
panic("MarshalText called on type with MarshalYAML")
}
func (o marshalerType) MarshalYAML() (interface{}, error) {
return o.value, nil
}
type marshalerValue struct {
Field marshalerType "_"
}
func (s *S) TestMarshaler(c *C) {
for _, item := range marshalerTests {
obj := &marshalerValue{}
obj.Field.value = item.value
data, err := yaml.Marshal(obj)
c.Assert(err, IsNil)
c.Assert(string(data), Equals, string(item.data))
}
}
func (s *S) TestMarshalerWholeDocument(c *C) {
obj := &marshalerType{}
obj.value = map[string]string{"hello": "world!"}
data, err := yaml.Marshal(obj)
c.Assert(err, IsNil)
c.Assert(string(data), Equals, "hello: world!\n")
}
type failingMarshaler struct{}
func (ft *failingMarshaler) MarshalYAML() (interface{}, error) {
return nil, failingErr
}
func (s *S) TestMarshalerError(c *C) {
_, err := yaml.Marshal(&failingMarshaler{})
c.Assert(err, Equals, failingErr)
}
func (s *S) TestSortedOutput(c *C) {
order := []interface{}{
false,
true,
1,
uint(1),
1.0,
1.1,
1.2,
2,
uint(2),
2.0,
2.1,
"",
".1",
".2",
".a",
"1",
"2",
"a!10",
"a/2",
"a/10",
"a~10",
"ab/1",
"b/1",
"b/01",
"b/2",
"b/02",
"b/3",
"b/03",
"b1",
"b01",
"b3",
"c2.10",
"c10.2",
"d1",
"d12",
"d12a",
}
m := make(map[interface{}]int)
for _, k := range order {
m[k] = 1
}
data, err := yaml.Marshal(m)
c.Assert(err, IsNil)
out := "\n" + string(data)
last := 0
for i, k := range order {
repr := fmt.Sprint(k)
if s, ok := k.(string); ok {
if _, err = strconv.ParseFloat(repr, 32); s == "" || err == nil {
repr = `"` + repr + `"`
}
}
index := strings.Index(out, "\n"+repr+":")
if index == -1 {
c.Fatalf("%#v is not in the output: %#v", k, out)
}
if index < last {
c.Fatalf("%#v was generated before %#v: %q", k, order[i-1], out)
}
last = index
}
}

View File

@ -1,41 +0,0 @@
package yaml_test
import (
"fmt"
"log"
"gitee.com/johng/gf/third/gopkg.in/yaml.v2"
)
// An example showing how to unmarshal embedded
// structs from YAML.
type StructA struct {
A string `yaml:"a"`
}
type StructB struct {
// Embedded structs are not treated as embedded in YAML by default. To do that,
// add the ",inline" annotation below
StructA `yaml:",inline"`
B string `yaml:"b"`
}
var data = `
a: a string from struct A
b: a string from struct B
`
func ExampleUnmarshal_embedded() {
var b StructB
err := yaml.Unmarshal([]byte(data), &b)
if err != nil {
log.Fatalf("cannot unmarshal data: %v", err)
}
fmt.Println(b.A)
fmt.Println(b.B)
// Output:
// a string from struct A
// a string from struct B
}

View File

@ -1,12 +0,0 @@
package yaml_test
import (
. "gitee.com/johng/gf/third/gopkg.in/check.v1"
"testing"
)
func Test(t *testing.T) { TestingT(t) }
type S struct{}
var _ = Suite(&S{})