首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >golang源码分析:goconvey(2)

golang源码分析:goconvey(2)

作者头像
golangLeetcode
发布2026-03-18 18:11:34
发布2026-03-18 18:11:34
610
举报

接着我们看下第二种情况也就是非根上下文的场景,第一步也是通过discovery获取参数列表,初始化suit,这里和根context一样。

代码语言:javascript
复制
func (ctx *context) Convey(items ...interface{}) {
    entry := discover(items)
    // we're a branch, or leaf (on the wind)
    if entry.Test != nil {
        conveyPanic(extraGoTest)
    }
    if ctx.focus && !entry.Focus {
        return
    }
    var inner_ctx *context
    if ctx.executedOnce {
        var ok bool
        inner_ctx, ok = ctx.children[entry.Situation]
        if !ok {
            conveyPanic(differentConveySituations, entry.Situation)
        }
    } else {
        if _, ok := ctx.children[entry.Situation]; ok {
            conveyPanic(multipleIdenticalConvey, entry.Situation)
        }
        inner_ctx = &context{
            reporter: ctx.reporter,
            children: make(map[string]*context),
            expectChildRun: ctx.expectChildRun,
            focus:       entry.Focus,
            failureMode: ctx.failureMode.combine(entry.FailMode),
            stackMode:   ctx.stackMode.combine(entry.StackMode),
        }
        ctx.children[entry.Situation] = inner_ctx
    }
    if inner_ctx.shouldVisit() {
        ctxMgr.SetValues(gls.Values{nodeKey: inner_ctx}, func() {
            inner_ctx.conveyInner(entry.Situation, entry.Func)
        })
    }
}

然后通过用例名称获取子上下文,最后复制给当前context的children字段,接着就是把调用作为value存在tls存储中。

接着看下So方法,它先获取当前上下文,然后调用上下文的So方法

代码语言:javascript
复制
func So(actual interface{}, assert Assertion, expected ...interface{}) {
    mustGetCurrentContext().So(actual, assert, expected...)
}

如果获取上下文失败就panic,说明So必须在Convey内部,获取上下文和调用Convey的时候用的同一个方法:

代码语言:javascript
复制
func mustGetCurrentContext() *context {
    ctx := getCurrentContext()
    if ctx == nil {
        conveyPanic(noStackContext)
    }
    return ctx
}

接着看下具体的So方法,如果成功就上报成功,失败上报堆栈:

代码语言:javascript
复制
func (ctx *context) So(actual interface{}, assert Assertion, expected ...interface{}) {
    if result := assert(actual, expected...); result == assertionSuccess {
        ctx.assertionReport(reporting.NewSuccessReport())
    } else {
        ctx.assertionReport(reporting.NewFailureReport(result, ctx.shouldShowStack()))
    }
}

其中的Assertion是一个返回string的方法,如果成功的话返回空

代码语言:javascript
复制
type Assertion func(actual interface{}, expected ...interface{}) string
代码语言:javascript
复制
const assertionSuccess = ""

除了So方法还有SoMsg和SkipSo方法

代码语言:javascript
复制
func SoMsg(msg string, actual interface{}, assert Assertion, expected ...interface{}) {
    mustGetCurrentContext().SoMsg(msg, actual, assert, expected...)
}
代码语言:javascript
复制
func SkipSo(stuff ...interface{}) {
    mustGetCurrentContext().SkipSo()
}

前者在So的基础上多了一个提示msg,会上报这个msg

代码语言:javascript
复制
func (ctx *context) SoMsg(msg string, actual interface{}, assert Assertion, expected ...interface{}) {
    if result := assert(actual, expected...); result == assertionSuccess {
        ctx.assertionReport(reporting.NewSuccessReport())
        return
    } else {
        ctx.reporter.Enter(reporting.NewScopeReport(msg))
        defer ctx.reporter.Exit()
        ctx.assertionReport(reporting.NewFailureReport(result, ctx.shouldShowStack()))
    }
}

后者不执行直接上报

代码语言:javascript
复制
func (ctx *context) SoMsg(msg string, actual interface{}, assert Assertion, expected ...interface{}) {
    if result := assert(actual, expected...); result == assertionSuccess {
        ctx.assertionReport(reporting.NewSuccessReport())
        return
    } else {
        ctx.reporter.Enter(reporting.NewScopeReport(msg))
        defer ctx.reporter.Exit()
        ctx.assertionReport(reporting.NewFailureReport(result, ctx.shouldShowStack()))
    }
}

Reset方法就简单了,直接方法入队列

代码语言:javascript
复制
func (ctx *context) Reset(action func()) {
    /* TODO: Failure mode configuration */
    ctx.resets = append(ctx.resets, action)
}

需要注意的是,ctx的SetValue和普通的的Set不同,它在set的时候会执行传入的函数,子ctx会继承父上下文的值并copy到当前上下文,这样就做到了不同上下文的隔离,如果没有传入值,就直接执行函数。

代码语言:javascript
复制
func (m *ContextManager) SetValues(new_values Values, context_call func()) {
    if len(new_values) == 0 {
        context_call()
        return
    }
    mutated_keys := make([]interface{}, 0, len(new_values))
    mutated_vals := make(Values, len(new_values))
    EnsureGoroutineId(func(gid uint) {
        m.mtx.Lock()
        state, found := m.values[gid]
        if !found {
            state = make(Values, len(new_values))
            m.values[gid] = state
        }
        m.mtx.Unlock()
        for key, new_val := range new_values {
            mutated_keys = append(mutated_keys, key)
            if old_val, ok := state[key]; ok {
                mutated_vals[key] = old_val
            }
            state[key] = new_val
        }
        defer func() {
            if !found {
                m.mtx.Lock()
                delete(m.values, gid)
                m.mtx.Unlock()
                return
            }
            for _, key := range mutated_keys {
                if val, ok := mutated_vals[key]; ok {
                    state[key] = val
                } else {
                    delete(state, key)
                }
            }
        }()
        context_call()
    })
}

去过获取groutine Id成功直接执行这个函数,否则,重新获取一个,然后执行。

代码语言:javascript
复制
func EnsureGoroutineId(cb func(gid uint)) {
    if gid, ok := readStackTag(); ok {
        cb(gid)
        return
    }
    gid := stackTagPool.Acquire()
    defer stackTagPool.Release(gid)
    addStackTag(gid, func() { cb(gid) })
}

通过封装保证了平级的Convey间相互隔离互不影响。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-06-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 golang算法架构leetcode技术php 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档