Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

View File

@@ -0,0 +1,70 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"cache_update_test.go",
"dao_test.go",
"follower_test.go",
"following_test.go",
"mysql_test.go",
"stat_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/job/main/relation/conf:go_default_library",
"//app/job/main/relation/model:go_default_library",
"//app/service/main/relation/model:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"cache_update.go",
"dao.go",
"follower.go",
"following.go",
"mysql.go",
"stat.go",
],
importpath = "go-common/app/job/main/relation/dao",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/job/main/relation/conf:go_default_library",
"//app/job/main/relation/model:go_default_library",
"//app/job/main/relation/model/i64b:go_default_library",
"//app/service/main/relation/model:go_default_library",
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/database/sql:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/time:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,135 @@
package dao
import (
"context"
"fmt"
"strconv"
"go-common/app/service/main/relation/model"
gmc "go-common/library/cache/memcache"
"go-common/library/cache/redis"
"go-common/library/log"
"go-common/library/time"
)
const (
_prefixFollowings = "at_"
_prefixTags = "tags_" // user tag info.
)
func tagsKey(mid int64) string {
return _prefixTags + strconv.FormatInt(mid, 10)
}
func followingsKey(mid int64) string {
return _prefixFollowings + strconv.FormatInt(mid, 10)
}
// ==== redis ===
// AddFollowingCache add following cache.
func (d *Dao) AddFollowingCache(c context.Context, mid int64, following *model.Following) (err error) {
var (
ok bool
key = followingsKey(mid)
)
conn := d.relRedis.Get(c)
if ok, err = redis.Bool(conn.Do("EXPIRE", key, d.relExpire)); err != nil {
log.Error("redis.Bool(conn.Do(EXPIRE, %s)) error(%v)", key, err)
} else if ok {
var ef []byte
if ef, err = d.encode(following.Attribute, following.MTime, following.Tag, following.Special); err != nil {
return
}
if _, err = conn.Do("HSET", key, following.Mid, ef); err != nil {
log.Error("conn.Do(HSET, %s, %d) error(%v)", key, following.Mid, err)
}
}
conn.Close()
return
}
// DelFollowing del following cache.
func (d *Dao) DelFollowing(c context.Context, mid int64, following *model.Following) (err error) {
var (
ok bool
key = followingsKey(mid)
)
conn := d.relRedis.Get(c)
if ok, err = redis.Bool(conn.Do("EXPIRE", key, d.relExpire)); err != nil {
log.Error("redis.Bool(conn.Do(EXPIRE, %s)) error(%v)", key, err)
} else if ok {
if _, err = conn.Do("HDEL", key, following.Mid); err != nil {
log.Error("conn.Do(HDEL, %s, %d) error(%v)", key, following.Mid, err)
}
}
conn.Close()
return
}
// encode
func (d *Dao) encode(attribute uint32, mtime time.Time, tagids []int64, special int32) (res []byte, err error) {
ft := &model.FollowingTags{Attr: attribute, Ts: mtime, TagIds: tagids, Special: special}
return ft.Marshal()
}
// ===== memcache =====
const (
_prefixFollowing = "pb_a_"
_prefixTagCount = "rs_tmtc_%d" // key of relation tag by mid & tag's count
)
func followingKey(mid int64) string {
return _prefixFollowing + strconv.FormatInt(mid, 10)
}
func tagCountKey(mid int64) string {
return fmt.Sprintf(_prefixTagCount, mid)
}
// DelFollowingCache delete following cache.
func (d *Dao) DelFollowingCache(c context.Context, mid int64) (err error) {
return d.delFollowingCache(c, followingKey(mid))
}
// delFollowingCache delete following cache.
func (d *Dao) delFollowingCache(c context.Context, key string) (err error) {
conn := d.mc.Get(c)
if err = conn.Delete(key); err != nil {
if err == gmc.ErrNotFound {
err = nil
} else {
log.Error("conn.Delete(%s) error(%v)", key, err)
}
}
conn.Close()
return
}
// DelTagCountCache del tag count cache.
func (d *Dao) DelTagCountCache(c context.Context, mid int64) (err error) {
conn := d.mc.Get(c)
if err = conn.Delete(tagCountKey(mid)); err != nil {
if err == gmc.ErrNotFound {
err = nil
} else {
log.Error("conn.Delete(%s) error(%v)", tagCountKey(mid), err)
}
}
conn.Close()
return
}
// DelTagsCache is
func (d *Dao) DelTagsCache(c context.Context, mid int64) (err error) {
conn := d.mc.Get(c)
if err = conn.Delete(tagsKey(mid)); err != nil {
if err == gmc.ErrNotFound {
err = nil
} else {
log.Error("conn.Delete(%s) error(%v)", tagCountKey(mid), err)
}
}
conn.Close()
return
}

View File

@@ -0,0 +1,156 @@
package dao
import (
"context"
"go-common/app/service/main/relation/model"
xtime "go-common/library/time"
"time"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaotagsKey(t *testing.T) {
var (
mid = int64(0)
)
convey.Convey("tagsKey", t, func(ctx convey.C) {
p1 := tagsKey(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaofollowingsKey(t *testing.T) {
var (
mid = int64(0)
)
convey.Convey("followingsKey", t, func(ctx convey.C) {
p1 := followingsKey(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaoAddFollowingCache(t *testing.T) {
var (
c = context.Background()
mid = int64(0)
following = &model.Following{}
)
convey.Convey("AddFollowingCache", t, func(ctx convey.C) {
err := d.AddFollowingCache(c, mid, following)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoDelFollowing(t *testing.T) {
var (
c = context.Background()
mid = int64(0)
following = &model.Following{}
)
convey.Convey("DelFollowing", t, func(ctx convey.C) {
err := d.DelFollowing(c, mid, following)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoencode(t *testing.T) {
var (
attribute = uint32(0)
mtime = xtime.Time(time.Now().Unix())
tagids = []int64{}
special = int32(0)
)
convey.Convey("encode", t, func(ctx convey.C) {
res, err := d.encode(attribute, mtime, tagids, special)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
}
func TestDaofollowingKey(t *testing.T) {
var (
mid = int64(0)
)
convey.Convey("followingKey", t, func(ctx convey.C) {
p1 := followingKey(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaotagCountKey(t *testing.T) {
var (
mid = int64(0)
)
convey.Convey("tagCountKey", t, func(ctx convey.C) {
p1 := tagCountKey(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaoDelFollowingCache(t *testing.T) {
var (
c = context.Background()
mid = int64(0)
)
convey.Convey("DelFollowingCache", t, func(ctx convey.C) {
err := d.DelFollowingCache(c, mid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaodelFollowingCache(t *testing.T) {
var (
c = context.Background()
key = ""
)
convey.Convey("delFollowingCache", t, func(ctx convey.C) {
err := d.delFollowingCache(c, key)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoDelTagCountCache(t *testing.T) {
var (
c = context.Background()
mid = int64(0)
)
convey.Convey("DelTagCountCache", t, func(ctx convey.C) {
err := d.DelTagCountCache(c, mid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoDelTagsCache(t *testing.T) {
var (
c = context.Background()
mid = int64(0)
)
convey.Convey("DelTagsCache", t, func(ctx convey.C) {
err := d.DelTagsCache(c, mid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}

View File

@@ -0,0 +1,59 @@
package dao
import (
"context"
"time"
"go-common/app/job/main/relation/conf"
"go-common/library/cache/memcache"
"go-common/library/cache/redis"
xsql "go-common/library/database/sql"
bm "go-common/library/net/http/blademaster"
)
// Dao struct info of Dao.
type Dao struct {
c *conf.Config
client *bm.Client
//path
clearFollowingPath string
clearFollowerPath string
clearStatPath string
// api path
followersNotify string
// db
db *xsql.DB
// redis
// redis *redis.Pool
// redisExpire int32
// relation cache
relRedis *redis.Pool
relExpire int32
mc *memcache.Pool
// UnreadDuration int64
}
// New new a Dao and return.
func New(c *conf.Config) (dao *Dao) {
dao = &Dao{
c: c,
client: bm.NewClient(c.HTTPClient),
clearFollowingPath: c.ClearPath.Following,
clearFollowerPath: c.ClearPath.Follower,
clearStatPath: c.ClearPath.Stat,
followersNotify: c.ApiPath.FollowersNotify,
db: xsql.NewMySQL(c.Mysql),
// redis: redis.NewPool(c.Redis.Config),
// redisExpire: int32(time.Duration(c.RelRedis.Expire) / time.Second),
relRedis: redis.NewPool(c.RelRedis.Config),
relExpire: int32(time.Duration(c.RelRedis.Expire) / time.Second),
mc: memcache.NewPool(c.Memcache.Config),
// UnreadDuration: int64(time.Duration(c.Relation.FollowersUnread) / time.Second),
}
return dao
}
// Ping ping health of db.
func (d *Dao) Ping(c context.Context) (err error) {
return d.db.Ping(c)
}

View File

@@ -0,0 +1,33 @@
package dao
import (
"flag"
"go-common/app/job/main/relation/conf"
"os"
"testing"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.account.relation-job")
flag.Set("conf_token", "3c29797a4f1c9939b24d36cdc62a3a2b")
flag.Set("tree_id", "2138")
flag.Set("conf_version", "docker-1")
flag.Set("deploy_env", "uat")
flag.Set("conf_host", "config.bilibili.co")
flag.Set("conf_path", "/tmp")
flag.Set("region", "sh")
flag.Set("zone", "sh001")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
m.Run()
os.Exit(0)
}

View File

@@ -0,0 +1,32 @@
package dao
import (
"context"
"net/url"
"strconv"
"go-common/library/log"
)
// consts
const (
AttrFollowing = uint32(1) << 1
AttrFriend = uint32(1) << 2
)
// DelFollowerCache del follower cache
func (d *Dao) DelFollowerCache(fid int64) (err error) {
params := url.Values{}
params.Set("mid", strconv.FormatInt(fid, 10))
var res struct {
Code int `json:"code"`
}
if err = d.client.Post(context.TODO(), d.clearFollowerPath, "", params, &res); err != nil {
log.Error("d.client.Post error(%v)", err)
return
}
if res.Code != 0 {
log.Error("url(%s) res code(%d) or res.result(%v)", d.clearFollowerPath+"?"+params.Encode(), res.Code)
}
return
}

View File

@@ -0,0 +1,19 @@
package dao
import (
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoDelFollowerCache(t *testing.T) {
var (
fid = int64(0)
)
convey.Convey("DelFollowerCache", t, func(ctx convey.C) {
err := d.DelFollowerCache(fid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}

View File

@@ -0,0 +1,35 @@
package dao
import (
"context"
"net/url"
"strconv"
"time"
"go-common/app/job/main/relation/model"
"go-common/library/log"
)
// UpdateFollowingCache update following cache.
func (d *Dao) UpdateFollowingCache(r *model.Relation) (err error) {
params := url.Values{}
params.Set("mid", strconv.FormatInt(r.Mid, 10))
params.Set("fid", strconv.FormatInt(r.Fid, 10))
params.Set("attribute", strconv.FormatInt(int64(r.Attribute), 10))
mt, err := time.Parse(time.RFC3339, r.MTime)
if err != nil {
mt = time.Now()
}
params.Set("mtime", strconv.FormatInt(mt.Unix(), 10))
var res struct {
Code int `json:"code"`
}
if err = d.client.Post(context.TODO(), d.clearFollowingPath, "", params, &res); err != nil {
log.Error("d.client.Post error(%v)", err)
return
}
if res.Code != 0 {
log.Error("url(%s) res code(%d) or res.result(%v)", d.clearFollowingPath+"?"+params.Encode(), res.Code)
}
return
}

View File

@@ -0,0 +1,20 @@
package dao
import (
"go-common/app/job/main/relation/model"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoUpdateFollowingCache(t *testing.T) {
var (
r = &model.Relation{}
)
convey.Convey("UpdateFollowingCache", t, func(ctx convey.C) {
err := d.UpdateFollowingCache(r)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}

View File

@@ -0,0 +1,57 @@
package dao
import (
"context"
"fmt"
"go-common/app/job/main/relation/model/i64b"
sml "go-common/app/service/main/relation/model"
"go-common/library/database/sql"
)
const (
_shard = 500
_tagUserShard = 500
// following
_getRelationSQL = "SELECT r.attribute,r.mtime,t.tag FROM user_relation_mid_%03d AS r join user_relation_tag_user_%03d AS t ON t.mid=r.mid AND t.fid=r.fid WHERE r.mid=? AND r.fid=? AND r.status=0 "
_UserSetAchieveFlag = "INSERT INTO user_addit (mid,achieve_flags) VALUES (?,?) ON DUPLICATE KEY UPDATE achieve_flags=achieve_flags|VALUES(achieve_flags)"
)
func hit(id int64) int64 {
return id % _shard
}
func tagUserHit(id int64) int64 {
return id % _tagUserShard
}
// UserRelation get user relation attr.
func (d *Dao) UserRelation(c context.Context, mid, fid int64) (f *sml.Following, err error) {
row := d.db.QueryRow(c, fmt.Sprintf(_getRelationSQL, hit(mid), tagUserHit(mid)), mid, fid)
f = new(sml.Following)
var ttag i64b.Int64Bytes
if err = row.Scan(&f.Attribute, &f.MTime, &ttag); err != nil {
if err == sql.ErrNoRows {
err = nil
f = nil
}
return
}
f.Mid = fid
f.Tag = []int64(ttag)
for _, id := range f.Tag {
if id == -10 {
f.Special = 1
}
}
return
}
// UserSetAchieveFlag is
func (d *Dao) UserSetAchieveFlag(ctx context.Context, mid int64, flag uint64) (int64, error) {
res, err := d.db.Exec(ctx, _UserSetAchieveFlag, mid, flag)
if err != nil {
return 0, err
}
return res.RowsAffected()
}

View File

@@ -0,0 +1,62 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaohit(t *testing.T) {
var (
id = int64(0)
)
convey.Convey("hit", t, func(ctx convey.C) {
p1 := hit(id)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaotagUserHit(t *testing.T) {
var (
id = int64(0)
)
convey.Convey("tagUserHit", t, func(ctx convey.C) {
p1 := tagUserHit(id)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaoUserRelation(t *testing.T) {
var (
c = context.Background()
mid = int64(0)
fid = int64(0)
)
convey.Convey("UserRelation", t, func(ctx convey.C) {
f, err := d.UserRelation(c, mid, fid)
ctx.Convey("Then err should be nil.f should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(f, convey.ShouldNotBeNil)
})
})
}
func TestDaoUserSetAchieveFlag(t *testing.T) {
var (
c = context.Background()
mid = int64(0)
flag = uint64(0)
)
convey.Convey("UserSetAchieveFlag", t, func(ctx convey.C) {
p1, err := d.UserSetAchieveFlag(c, mid, flag)
ctx.Convey("Then err should be nil.p1 should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(p1, convey.ShouldNotBeNil)
})
})
}

View File

@@ -0,0 +1,111 @@
package dao
import (
"context"
"fmt"
"math"
"net/url"
"strconv"
"go-common/app/job/main/relation/model"
"go-common/library/log"
)
// DelStatCache is
func (d *Dao) DelStatCache(mid int64) (err error) {
params := url.Values{}
params.Set("mid", strconv.FormatInt(mid, 10))
var res struct {
Code int `json:"code"`
}
if err = d.client.Post(context.TODO(), d.clearStatPath, "", params, &res); err != nil {
log.Error("d.client.Post error(%v)", err)
return
}
if res.Code != 0 {
log.Error("url(%s) res code(%d) or res.result(%v)", d.clearStatPath+"?"+params.Encode(), res.Code)
}
return
}
// FollowerAchieve is
func (d *Dao) FollowerAchieve(c context.Context, mid, follower int64) {
// 不为 0 结尾的就不检查了
if follower%10 != 0 {
return
}
flag := model.AchieveFromFollower(follower)
if flag <= 0 {
log.Warn("No achieve flag achieved with mid: %d, follower: %d", mid, follower)
return
}
effected, err := d.UserSetAchieveFlag(c, mid, uint64(flag))
if err != nil {
log.Error("Failed to set user achieve flag: mid: %d, flag: %d: %+v", mid, flag, err)
return
}
if effected <= 0 {
log.Info("Already achieved with mid: %d, flag: %d", mid, flag)
return
}
msg := func() string {
switch flag {
case model.FollowerAchieve1k:
return `恭喜您您的粉丝已经达到1000粉`
case model.FollowerAchieve5k:
return `恭喜您您的粉丝已经达到5000粉`
case model.FollowerAchieve10k:
return `恭喜您您的粉丝已经达到1万粉您将有机会获得UP主粉丝成就奖“一万粉丝成就奖励” #{戳我领取吧!}{"https://www.bilibili.com/blackboard/activity-zxIQ8otdK.html#/"}`
case model.FollowerAchieve100k:
return `恭喜您您的粉丝已经达到10万粉您将有机会获得UP主粉丝成就奖“十万粉丝成就奖励” #{戳我领取吧!}{"https://www.bilibili.com/blackboard/activity-zxIQ8otdK.html#/"}`
case model.FollowerAchieve1000k:
return `恭喜您您的粉丝已经达到100万粉您将有机会获得UP主粉丝成就奖“百万粉丝成就奖励” #{戳我领取吧!}{"https://www.bilibili.com/blackboard/activity-zxIQ8otdK.html#/"}`
}
if flag >= model.FollowerAchieve100k {
return fmt.Sprintf(`恭喜您,您的粉丝已达%d万粉`, int64((math.Log2(float64(flag))-2)*100000/10000))
}
return ""
}()
if msg != "" {
log.Info("Follower achieve send message to mid: %d: %s", mid, msg)
d.SendMsg(c, mid, "粉丝增长通知", msg)
}
d.ensureAllFollowerAchieve(c, mid, follower)
}
func (d *Dao) ensureAllFollowerAchieve(c context.Context, mid int64, follower int64) {
flags := model.AllAchieveFromFollower(follower)
v := model.AchieveFlag(0)
for _, f := range flags {
v |= f
}
effected, err := d.UserSetAchieveFlag(c, mid, uint64(v))
if err != nil {
log.Error("Failed to ensure user achieve flag: mid: %d, flags: %+v, follower: %d: %+v", mid, flags, follower, err)
return
}
if effected >= 0 {
log.Warn("Achieve missed on mid: %d, follower: %d, flags: %+v", mid, follower, flags)
return
}
}
// SendMsg send message.
func (d *Dao) SendMsg(c context.Context, mid int64, title string, context string) (err error) {
params := url.Values{}
params.Set("mc", "2_5_1")
params.Set("title", title)
params.Set("data_type", "4")
params.Set("context", context)
params.Set("mid_list", fmt.Sprintf("%d", mid))
var res struct {
Code int `json:"code"`
}
if err = d.client.Post(c, d.followersNotify, "", params, &res); err != nil || res.Code != 0 {
log.Error("sendMsgURL(%s) code(%d) error(%v)", d.followersNotify+"?"+params.Encode(), res.Code, err)
return
}
log.Info("d.sendMsgURL url(%s) res(%d)", d.followersNotify+"?"+params.Encode(), res.Code)
return
}

View File

@@ -0,0 +1,61 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoDelStatCache(t *testing.T) {
var (
mid = int64(0)
)
convey.Convey("DelStatCache", t, func(ctx convey.C) {
err := d.DelStatCache(mid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoFollowerAchieve(t *testing.T) {
var (
c = context.Background()
mid = int64(0)
follower = int64(0)
)
convey.Convey("FollowerAchieve", t, func(ctx convey.C) {
d.FollowerAchieve(c, mid, follower)
ctx.Convey("No return values", func(ctx convey.C) {
})
})
}
func TestDaoensureAllFollowerAchieve(t *testing.T) {
var (
c = context.Background()
mid = int64(0)
follower = int64(0)
)
convey.Convey("ensureAllFollowerAchieve", t, func(ctx convey.C) {
d.ensureAllFollowerAchieve(c, mid, follower)
ctx.Convey("No return values", func(ctx convey.C) {
})
})
}
func TestDaoSendMsg(t *testing.T) {
var (
c = context.Background()
mid = int64(0)
title = ""
context = ""
)
convey.Convey("SendMsg", t, func(ctx convey.C) {
err := d.SendMsg(c, mid, title, context)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}