首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >goc源码阅读

goc源码阅读

作者头像
golangLeetcode
发布2026-03-18 18:30:26
发布2026-03-18 18:30:26
650
举报

golang源码的工具链中提供了可选参数coverprofile参数,可以生成覆盖率文件,方便我们快速查看,哪行代码被覆盖了,哪行代码还需要验证,但是只支持单测环境。如果我们想在运行时刻实时统计代码的覆盖率,特别是不太在意性能损失的测试环境,就无能为力了。

于是https://github.com/qiniu/goc就诞生了,它借鉴了golang官方覆盖率统计方案和部分代码,核心原理是在编译打包的时候创建一个临时目录,将源码插桩后放到临时目录,然后编译成带覆盖率的包,用户访问代码的时候,桩实时统计访问次数,然后通过websocket上报给前端,这样就可以实时查看代码的覆盖率了。下面开始研究下源码:

入口文件是goc.go,它只是注册了各种命令行参数,使用了spf13包,具体源码参考往期博客,这里不再详述。

代码语言:javascript
复制
func main() {
	cmd.Execute()

根命令行注册了各个具体的子命令cmd/root.go

代码语言:javascript
复制
func Execute() {
if err := rootCmd.Execute(); err != nil {
var rootCmd = &cobra.Command{
			PersistentPreRun: func(cmd *cobra.Command, args []string) {
			PersistentPostRun: func(cmd *cobra.Command, args []string) {

我们核心关注的覆盖率相关的命令源码位于cmd/cover.go,在代码包初始化的时候注册进去了

代码语言:javascript
复制
func init() {
	rootCmd.AddCommand(coverCmd)

其中变量定义如下,最终执行力runCover方法

代码语言:javascript
复制
var coverCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
runCover(target)
	},

它先初始化了CoverInfo结构体,组装了运行需要的上下文和参数,然后调用Execute方法:

代码语言:javascript
复制
func runCover(target string) {
	ci := &cover.CoverInfo{
			_ = cover.Execute(ci)

结构体定义位于pkg/cover/cover.go,具体执行的方法也位于同一个包下面:

代码语言:javascript
复制
type CoverInfo struct {
	Target                   string
	GoPath                   string
	IsMod                    bool
	ModRootPath              string
	GlobalCoverVarImportPath string // path for the injected global cover var file
	OneMainPackage           bool
	Args                     string
	Mode                     string
	AgentPort                string
	Center                   string
	Singleton                bool
}

核心代码分为下面三步:1,根据参数列出需要处理的包 2,调用AddCounters进行源码打桩,将计数代码的赋值逻辑按照源码的作用域进行打桩 3,将打桩依赖的变量声明放到一个单独的包里,让各个桩代码引用。

代码语言:javascript
复制
func Execute(coverInfo *CoverInfo) error {
	pkgs, err := ListPackages(target, strings.Join(listArgs, " "), newGopath)
if pkg.Name == "main" {
         	mainCover, mainDecl := AddCounters(pkg, mode, globalCoverVarImportPath)
  return injectGlobalCoverVarFile(coverInfo, allDecl)

获取需要处理的包最为简单,直接使用了go list命令,列出所有需要的包

代码语言:javascript
复制
func ListPackages(dir string, args string, newgopath string) (map[string]*Package, error) {
	cmd := exec.Command("/bin/bash", "-c", "go list "+args)

然后就是打桩代码,先根据作用域编号进行声明变量,然后调用Annotate进行打桩,将结果写入decl变量

代码语言:javascript
复制
func AddCounters(pkg *Package, mode string, globalCoverVarImportPath string) (*PackageCover, string) {
	coverVarMap := declareCoverVars(pkg)
	decl := ""
for file, coverVar := range coverVarMap {
		decl += "\n" + tool.Annotate(path.Join(pkg.Dir, file), mode, coverVar.Var, globalCoverVarImportPath) + "\n"
	}

声明的变量都是GoCover开头的

代码语言:javascript
复制
func declareCoverVars(p *Package) map[string]*FileVar {
	coverVars[file] = &FileVar{
			File: longFile,
			Var:  fmt.Sprintf("GoCover_%d_%x", coverIndex, h),
		}

打桩代码位于pkg/cover/internal/tool/cover.go,它通过解析golang源码的语法树,然后调用edit编辑器进行代码插桩改造,最后引入声明的桩结构类型

代码语言:javascript
复制
func Annotate(name string, mode string, varVar string, globalCoverVarImportPath string) string {
	fset := token.NewFileSet()
	parsedFile, err := parser.ParseFile(fset, name, content, parser.ParseComments)
	file.edit.Insert(file.offset(file.astFile.Name.End()),
	fmt.Sprintf("; import %s %q", ".", globalCoverVarImportPath))

最后一步就是将生成的带桩代码写入临时目录pkg/cover/instrument.go

代码语言:javascript
复制
func injectGlobalCoverVarFile(ci *CoverInfo, content string) error {
	coverFile, err := os.Create(filepath.Join(ci.Target, ci.GlobalCoverVarImportPath, "cover.go"))
	_, err = coverFile.WriteString(content)

类似的命令还有cmd/profile.go

代码语言:javascript
复制
var profileCmd = &cobra.Command{
	Run: func(cmd *cobra.Command, args []string) {
	p := cover.ProfileParam{
			Force:             force,
			Service:           svrList,
			Address:           addrList,
			CoverFilePatterns: coverFilePatterns,
			SkipFilePatterns:  skipFilePatterns,
		}
       res, err := cover.NewWorker(center).Profile(p)

以及build,因为编译的时候打桩cmd/build.go

代码语言:javascript
复制
var buildCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
runBuild(args, wd)
代码语言:javascript
复制
func runBuild(args []string, wd string) {
			gocBuild, err := build.NewBuild(buildFlags, args, wd, buildOutput)
defer gocBuild.Clean()
			ci := &cover.CoverInfo{
			err = cover.Execute(ci)
			err = gocBuild.Build()

在pkg/build/build.go 中完成代码的复制

代码语言:javascript
复制
func NewBuild(buildflags string, args []string, workingDir string, outputDir string) (*Build, error) {
    if err := b.MvProjectsToTmp(); err != nil {
    dir, err := b.determineOutputDir(outputDir)
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-08-31,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

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