资讯详情 syzkaller 与 cloud.google.com/go:用 Fakes、Mocks 与 Emulators 测试 Google Cloud Go 客户端代码
📅 2026/10/13 6:08:17
测试操作系统网络安全【免费下载链接】syzkallersyzkaller is an unsupervised coverage-guided kernel fuzzer项目地址https://gitcode.com/gh_mirrors/syz/syzkaller点击查看免费下载在 vendor/cloud.google.com/go/testing.md 这份随仓库 vendor 进来的官方文档中Google 给出了测试依赖cloud.google.com/go客户端库代码的三种主流模式基于内存 gRPC fake server 的测试、基于接口 mock 的测试以及基于服务模拟器的测试。syzkaller 本身就在多个模块中直接使用这些客户端库——dashboard 用 spanner 客户端 存储覆盖率历史pkg/gcs 封装了 GCS 对象存储pkg/kcidb 通过 pubsub 向 KCIDB 推送 bug 报告。读完本文你将掌握如何在测试中绕过真实的 GCP 服务并能结合客户端库源码理解每种模式背后的机制。背景具体类型而非接口cloud.google.com/go生成的所有客户端库都采取返回具体类型concrete type而不是接口的策略。这样做的好处是库可以在后续版本中自由添加新的字段和方法而不会破坏下游用户的编译。代价是如果想在测试中替换客户端的行为就不能简单地依赖注入一个接口而需要采用本文介绍的模式。syzkaller 的代码正体现了这种直接持有具体类型的写法。例如 pkg/gcs/gcs.go 中的包装器type Client struct { client *storage.Client ctx context.Context } func NewClient() (*Client, error) { ctx : context.Background() storageClient, err : storage.NewClient(ctx) ... }以及 pkg/kcidb/client.go 中直接持有*pubsub.Clienttype Client struct { ctx context.Context origin string client *pubsub.Client topic *pubsub.Topic }这些代码在生产中依赖 Application Default Credentials 直连 GCP要在测试或本地环境中运行等价逻辑就需要下面三种方案。模式一用 Fake Server 测试 gRPC 服务cloud.google.com/go中的客户端基本都是 gRPC 协议的少数例外是storage和bigquery客户端它们主要走 HTTP。gRPC 服务可以通过在测试内部启动一个自己的内存 gRPC server来伪造其核心优势是运行时代码完全不需要定义接口继续直接使用具体结构体类型伪造逻辑全部放在测试代码里。完整示例可参考 google-cloud-go 官方仓库中的internal/examples/fake目录vendor 文档中给出的对应示例。被测函数使用具体类型客户端先看一个使用translate.TranslationClient具体类型的函数import ( context fmt log os translate cloud.google.com/go/translate/apiv3 github.com/googleapis/gax-go/v2 translatepb google.golang.org/genproto/googleapis/cloud/translate/v3 ) func TranslateTextWithConcreteClient(client *translate.TranslationClient, text string, targetLang string) (string, error) { ctx : context.Background() log.Printf(Translating %q to %q, text, targetLang) req : translatepb.TranslateTextRequest{ Parent: fmt.Sprintf(projects/%s/locations/global, os.Getenv(GOOGLE_CLOUD_PROJECT)), TargetLanguageCode: en-US, Contents: []string{text}, } resp, err : client.TranslateText(ctx, req) if err ! nil { return , fmt.Errorf(unable to translate text: %v, err) } translations : resp.GetTranslations() if len(translations) ! 1 { return , fmt.Errorf(expected only one result, got %d, len(translations)) } return translations[0].TranslatedText, nil }被测代码持有的是*translate.TranslationClient具体类型没有任何接口抽象——这正是该模式要解决的场景。编写 Fake Servergoogle.golang.org/genproto中所有生成的 protobuf 代码都包含一个package.UnimplementedFooServer类型非常适合用来创建 fake。把未实现的 server 嵌入到 fake 结构中fake 就会继承该 server 暴露的所有 RPC然后只需为自己要伪造的那一个 RPC 提供实现方法即可覆盖默认的 unimplemented 行为import ( context translatepb google.golang.org/genproto/googleapis/cloud/translate/v3 ) type fakeTranslationServer struct { translatepb.UnimplementedTranslationServiceServer } func (f *fakeTranslationServer) TranslateText(ctx context.Context, req *translatepb.TranslateTextRequest) (*translatepb.TranslateTextResponse, error) { resp : translatepb.TranslateTextResponse{ Translations: []*translatepb.Translation{ translatepb.Translation{ TranslatedText: Hello World, }, }, } return resp, nil }测试的完整搭建fake 方案需要一点 setup启动一个net.Listener注册 fake server并让客户端库指向这个本地 serverimport ( context net testing translate cloud.google.com/go/translate/apiv3 google.golang.org/api/option translatepb google.golang.org/genproto/googleapis/cloud/translate/v3 google.golang.org/grpc google.golang.org/grpc/credentials/insecure ) func TestTranslateTextWithConcreteClient(t *testing.T) { ctx : context.Background() // Setup the fake server. fakeTranslationServer : fakeTranslationServer{} l, err : net.Listen(tcp, localhost:0) if err ! nil { t.Fatal(err) } gsrv : grpc.NewServer() translatepb.RegisterTranslationServiceServer(gsrv, fakeTranslationServer) fakeServerAddr : l.Addr().String() go func() { if err : gsrv.Serve(l); err ! nil { panic(err) } }() // Create a client. client, err : translate.NewTranslationClient(ctx, option.WithEndpoint(fakeServerAddr), option.WithoutAuthentication(), option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())), ) if err ! nil { t.Fatal(err) } // Run the test. text, err : TranslateTextWithConcreteClient(client, Hola Mundo, en-US) if err ! nil { t.Fatal(err) } if text ! Hello World { t.Fatalf(got %q, want Hello World, text) } }关键参数有三处option.WithEndpoint把客户端指向本地地址option.WithoutAuthentication()关闭认证本地没有 GCP 凭据grpc.WithTransportCredentials(insecure.NewCredentials())使用明文传输。net.Listen(tcp, localhost:0)中的端口0让操作系统自动分配空闲端口避免测试间端口冲突。模式二用 Mock 测试mock 方案与 fake 相反它必须围绕接口展开。以文档中的例子为例为TranslateTextWithConcreteClient用到的cloud.google.com/go/translate/apiv3客户端定义一个只含实际调用方法的接口translate.Client有十几个方法但这里只用了一个type TranslationClient interface { TranslateText(ctx context.Context, req *translatepb.TranslateTextRequest, opts ...gax.CallOption) (*translatepb.TranslateTextResponse, error) }然后把函数签名改为接收接口func TranslateTextWithInterfaceClient(client TranslationClient, text string, targetLang string) (string, error) { // ... }这样生产中传入真实的translate.Client测试时传入 mock 实现。该模式可以应用于任何 Go 代码而不仅限于cloud.google.com/go原因在于Go 的接口是隐式满足的客户端库中的具体结构体可以隐式实现你在自己代码库中定义的接口无需修改库本身。一个轻量的手写 mock 如下import ( context testing github.com/googleapis/gax-go/v2 translatepb google.golang.org/genproto/googleapis/cloud/translate/v3 ) type mockClient struct{} func (*mockClient) TranslateText(_ context.Context, req *translatepb.TranslateTextRequest, opts ...gax.CallOption) (*translatepb.TranslateTextResponse, error) { resp : translatepb.TranslateTextResponse{ Translations: []*translatepb.Translation{ translatepb.Translation{ TranslatedText: Hello World, }, }, } return resp, nil } func TestTranslateTextWithAbstractClient(t *testing.T) { client : mockClient{} text, err : TranslateTextWithInterfaceClient(client, Hola Mundo, en-US) if err ! nil { t.Fatal(err) } if text ! Hello World { t.Fatalf(got %q, want Hello World, text) } }如果不想手写 mock可以使用golang/mock之类的框架从接口自动生成 mock。文档同时提醒不要过度使用 mock——mock 应当只针对被测代码真正调用的行为否则会形成脆弱的测试耦合。模式三用模拟器Emulator测试cloud.google.com/go中的部分客户端支持对服务模拟器运行。概念上与 fake 类似但 server 由模拟器托管你只需启动模拟器并设置服务对应的环境变量让客户端库把流量发到模拟器。当前支持的服务与环境变量为服务环境变量bigtableBIGTABLE_EMULATOR_HOSTdatastoreDATASTORE_EMULATOR_HOSTfirestoreFIRESTORE_EMULATOR_HOSTpubsubPUBSUB_EMULATOR_HOSTspannerSPANNER_EMULATOR_HOSTstorageSTORAGE_EMULATOR_HOSTstorage 客户端支持该环境变量但 gcloud 没有提供官方模拟器各客户端对模拟器的支持可以在 vendor 目录的源码中逐一确认Spanner 客户端vendor/cloud.google.com/go/spanner/doc.go 的包文档明确说明设置SPANNER_EMULATOR_HOST指向模拟器地址后请求就会被发到该地址而不是 Cloud Spanner之后像平常一样创建客户端即可// Set SPANNER_EMULATOR_HOST environment variable. err : os.Setenv(SPANNER_EMULATOR_HOST, localhost:9010) ... // Create client as usual. client, err : spanner.NewClient(ctx, projects/P/instances/I/databases/D)syzkaller 中 dashboard/app/entities_spanner.go 正是这种平常创建客户端的写法——spanner.NewClient连接projects/$GOOGLE_CLOUD_PROJECT/instances/syzbot/databases/coverage后执行spanner.Statement查询。从源码结构看只要本地环境设置了SPANNER_EMULATOR_HOST同一份代码就可以无修改地跑在本地模拟器上。vendor 目录下还附带了一个完整的模拟器测试脚本 vendor/cloud.google.com/go/spanner/emulator_test.sh展示了 CI 中如何操作export SPANNER_EMULATOR_HOSTlocalhost:9010 export GCLOUD_TESTS_GOLANG_PROJECT_IDemulator-test-project # 下载并启动模拟器 ./emulator_main --host_port $SPANNER_EMULATOR_HOST ... # 只运行集成测试 go test -v -timeout 10m ./... -run ^TestIntegration_注意两个细节集成测试用TestIntegration_前缀隔离避免与纯单元测试混跑脚本用trap cleanup EXIT在退出时终止模拟器并清理环境变量。Storage 客户端vendor/cloud.google.com/go/storage/storage.go 中NewClient的初始化逻辑清晰展示了STORAGE_EMULATOR_HOST的处理方式环境变量未设置时走默认 endpointhttps://storage.googleapis.com一族并尝试用 Application Default Credentials 获取凭据环境变量已设置时解析该地址未带 scheme 时自动补http://拼上storage/v1/路径作为默认 endpoint并自动前缀option.WithoutAuthentication()——即连模拟器时免认证用户无需任何额外配置。syzkaller 的 pkg/gcs 模块封装了*storage.Client的读/写/删除/发布等操作注释中写明其假设程序运行在 GCE 上并使用 Application Default Credentials——这与 storage 客户端未设置模拟器变量时的默认路径完全对应。PubSub 客户端vendor/cloud.google.com/go/pubsub/pubsub.go 中NewClientWithConfig对PUBSUB_EMULATOR_HOST的处理与 fake 方案中的客户端配置如出一辙// Environment variables for gcloud emulator: if addr : os.Getenv(PUBSUB_EMULATOR_HOST); addr ! { emulatorOpts : []option.ClientOption{ option.WithEndpoint(addr), option.WithGRPCDialOption(grpc.WithInsecure()), option.WithoutAuthentication(), option.WithTelemetryDisabled(), internaloption.SkipDialSettingsValidation(), } opts append(emulatorOpts, opts...) } else { // 非模拟器场景多 gRPC 连接池 keepalive ... }可以看到模拟器模式本质上是客户端库内部帮你做了一遍 fake 方案中手工做的三件事指向本地 endpoint、关闭认证、禁用遥测。syzkaller 的 pkg/kcidb/client.go 通过pubsub.NewClient(ctx, project, option.WithCredentialsJSON(credentials))创建客户端后向 KCIDB 的 topic 发布 JSON 化的 bug 报告从源码结构看本地联调时设置PUBSUB_EMULATOR_HOST即可让这条发布路径指向本地模拟器。三种模式怎么选结合 go.mod 中 syzkaller 实际依赖的客户端版本cloud.google.com/go v0.114.0、storage v1.40.0、spanner v1.60.0、pubsub v1.38.0、secretmanager v1.13.1与上述源码可以归纳出选择依据Fake server被测代码直接使用具体类型客户端syzkaller 中 dashboard/app/entities_spanner.go、pkg/gce/gcp_secret.go 的 secretmanager 客户端均属此类且不便为运行时代码引入接口时选用。测试完全自包含不需要外部进程但需要自己搭建 gRPC server。Mock可以接受在代码库中定义窄接口时选用测试最简单、速度最快利用 Go 接口的隐式满足特性对客户端库零侵入。注意控制 mock 的粒度避免过度 mock。Emulator客户端库原生支持模拟器变量spanner、pubsub、storage、bigtable、datastore、firestore时选用客户端代码零改动即可切换到本地且行为比手写 fake 更接近真实服务。适用前提是本地能启动对应模拟器storage 目前无官方模拟器仅有环境变量支持。三种模式共同的前提是本地环境不依赖生产 GCP 凭据fake 与 emulator 路径下客户端库都会自动关闭认证option.WithoutAuthentication()这也是它们能脱离云环境运行的根本原因。赞分享测试操作系统网络安全【免费下载链接】syzkallersyzkaller is an unsupervised coverage-guided kernel fuzzer项目地址https://gitcode.com/gh_mirrors/syz/syzkaller点击查看免费下载相关推荐Tekton Pipeline 中如何测试依赖 Google Go 客户端库的代码fakes、mocks 与 emulators 实战指南Tekton Pipeline 中如何测试依赖 Google Go 客户端库的代码fakes、mocks 与 emulators 实战指南 本指南源自随 Te云原生CI/CDDevOps后端使用 Google Go Client Libraries 的测试策略fakes、mocks 与 emulators 实战指南使用 Google Go Client Libraries 的测试策略fakes、mocks 与 emulators 实战指南 导读 cloud.google人工智能AI AgentAgent 沙箱云原生容器运行时零信任深入解读 syzkaller 仓库中的 Google Cloud Go 客户端库cloud.google.com/go变更记录与落地实践深入解读 syzkaller 仓库中的 Google Cloud Go 客户端库cloud.google.com/go变更记录与落地实践 本文以 syzka测试操作系统网络安全上一篇告别文件孤岛WebDAV协议与zfile无缝集成实战指南下一篇简历排版黄金法则gh_mirrors/re/resume模板间距与布局优化全指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考