第一章:golang代码题
Go语言代码题常聚焦于并发模型、内存管理、接口设计与边界处理等核心能力。掌握典型题目不仅能提升编码熟练度,更能深入理解Go运行时机制。
并发安全的计数器实现
需避免竞态条件,推荐使用sync/atomic或sync.Mutex。以下为原子操作实现:
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
var counter int64
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
atomic.AddInt64(&counter, 1) // 原子递增,无需锁
}()
}
wg.Wait()
fmt.Println("Final count:", atomic.LoadInt64(&counter)) // 输出确定值:100
}
该实现规避了i++在多goroutine下的非原子性问题,atomic.AddInt64保证线程安全且性能优于互斥锁。
接口嵌套与类型断言实践
Go中接口可组合,但类型断言需谨慎处理panic风险:
type Speaker interface {
Speak() string
}
type Walker interface {
Walk() string
}
type Person struct{ Name string }
func (p Person) Speak() string { return p.Name + " says hello" }
func (p Person) Walk() string { return p.Name + " is walking" }
func demoInterface() {
var p interface{} = Person{Name: "Alice"}
if speaker, ok := p.(Speaker); ok {
fmt.Println(speaker.Speak()) // 安全断言,ok为true时才调用
}
}
常见陷阱速查表
| 问题类型 | 典型表现 | 推荐解法 |
|---|---|---|
| 切片扩容副作用 | 函数内append修改原切片底层数组 | 显式传入cap或返回新切片 |
| nil channel发送 | 阻塞或panic | 发送前判空或用select default |
| defer闭包变量捕获 | 打印循环末值而非预期迭代值 | 在defer中显式传参(如defer func(i int){...}(i)) |
掌握上述模式,可高效应对笔试与工程中的典型Go代码挑战。
第二章:CNCF项目源码改编题一:Kubernetes Client-go 并发控制器实现
2.1 控制器模式与 Informer 机制原理剖析
Kubernetes 控制器是声明式 API 的核心执行者,通过持续调谐(reconciliation)使实际状态趋近期望状态。
数据同步机制
Informer 采用“List-Watch”双阶段机制:先全量拉取(List),再长连接监听(Watch)增量事件。其内部包含三层关键组件:
- Reflector:负责与 API Server 建立 Watch 连接,将事件写入 DeltaFIFO 队列
- DeltaFIFO:存储对象变更(Added/Updated/Deleted/Sync)的有界队列
- Indexer:基于内存的线程安全缓存,支持按 namespace、label 等快速索引
informer := cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: listFunc, // 初始全量获取,返回 *corev1.PodList
WatchFunc: watchFunc, // 增量监听,返回 watch.Event 流
},
&corev1.Pod{}, // 对象类型
0, // resyncPeriod=0 表示禁用周期性重同步
cache.Indexers{}, // 可选索引器,如 cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc}
)
ListFunc返回带 ResourceVersion 的列表,作为 Watch 起始点;WatchFunc必须从该 ResourceVersion 开始监听,确保事件不丢不重。
控制流图示
graph TD
A[API Server] -->|List + RV| B(Reflector)
A -->|Watch Stream| B
B --> C[DeltaFIFO]
C --> D[Controller ProcessLoop]
D --> E[Indexer Cache]
E --> F[用户自定义 Handle]
| 组件 | 线程安全 | 持久化 | 主要职责 |
|---|---|---|---|
| DeltaFIFO | ✅ | ❌ | 事件缓冲与去重 |
| Indexer | ✅ | ❌ | 内存缓存 + 多维索引 |
| Controller Loop | ✅ | ❌ | 从 FIFO 消费并触发 Sync |
2.2 SharedIndexInformer 同步逻辑的 Go 实现与调试
数据同步机制
SharedIndexInformer 通过 Reflector 拉取全量资源,经 DeltaFIFO 缓存后由 Controller 驱动 ProcessLoop 消费。关键同步入口为 HandleDeltas 方法。
func (s *sharedIndexInformer) HandleDeltas(obj interface{}) error {
deltas, ok := obj.(Deltas)
if !ok { return fmt.Errorf("invalid object type") }
for _, d := range deltas {
s.cacheMutationDetector.AddObject(d.Object) // 检测对象篡改
switch d.Type {
case Sync, Added, Updated:
s.indexer.Add(d.Object) // 写入线程安全的ThreadSafeStore
case Deleted:
s.indexer.Delete(d.Object)
}
}
return nil
}
d.Type区分事件类型:Sync表示来自 ListWatch 的初始同步快照;Added/Updated/Deleted来自后续 Watch 增量。s.indexer是带索引能力的本地缓存,支持按 label/field 快速查询。
同步阶段关键组件对比
| 组件 | 职责 | 线程安全 | 触发时机 |
|---|---|---|---|
| Reflector | 调用 List/Watch API | ✅ | 启动时 & 连接断开重试 |
| DeltaFIFO | 存储带类型标记的变更 | ✅ | Watch 接收事件后入队 |
| Controller | 协调消费循环 | ✅ | 启动 Run() 后持续运行 |
同步流程概览
graph TD
A[Reflector.List] --> B[DeltaFIFO.Replace]
A --> C[Reflector.Watch]
C --> D[DeltaFIFO.QueueAction]
D --> E[Controller.ProcessLoop]
E --> F[HandleDeltas]
F --> G[Update indexer]
2.3 并发安全的 ResourceCache 设计与 sync.Map 应用实践
ResourceCache 需在高并发读写场景下保证数据一致性与低延迟。传统 map 配合 sync.RWMutex 存在读多写少时的锁竞争瓶颈,而 sync.Map 提供了无锁读、分片写优化的原生并发支持。
核心设计原则
- 读写分离:高频
Load操作完全无锁 - 值不可变性:缓存 value 为结构体指针,避免拷贝与竞态
- 过期协同:结合
time.Timer实现惰性驱逐
关键实现片段
type ResourceCache struct {
data *sync.Map // key: string, value: *cachedResource
}
type cachedResource struct {
Value interface{}
ExpiredAt time.Time
}
// 安全写入(含过期检查)
func (c *ResourceCache) Set(key string, val interface{}, ttl time.Duration) {
c.data.Store(key, &cachedResource{
Value: val,
ExpiredAt: time.Now().Add(ttl),
})
}
sync.Map.Store()内部采用分段哈希表+原子操作,避免全局锁;cachedResource为指针类型,确保Load返回值修改不影响缓存状态。ExpiredAt由调用方控制,解耦驱逐逻辑。
| 特性 | map + RWMutex | sync.Map |
|---|---|---|
| 并发读性能 | 中(需读锁) | 高(纯原子读) |
| 写放大 | 低 | 中(需复制桶) |
| 内存占用 | 紧凑 | 略高(冗余指针) |
graph TD
A[Get key] --> B{sync.Map.Load?}
B -->|存在且未过期| C[返回 value]
B -->|不存在/已过期| D[触发异步加载]
D --> E[Set 新值]
2.4 Reconcile 函数的幂等性保障与错误恢复策略
Reconcile 函数是控制器核心循环的执行单元,其设计必须天然支持多次调用不改变终态——即幂等性。
数据同步机制
控制器通过“获取当前状态 → 计算期望状态 → 执行差异操作”三步完成同步,每次调用均基于最新 API 对象快照,避免状态漂移。
错误恢复策略
- 遇临时错误(如 etcd timeout)自动重入队列,带指数退避
- 永久错误(如非法 YAML)触发事件告警并跳过本次 reconcile
- 状态字段(
.status.conditions)实时记录 lastTransitionTime 与 reason
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
var instance v1alpha1.MyApp
if err := r.Get(ctx, req.NamespacedName, &instance); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err) // 幂等:资源不存在即终止
}
// 基于 instance.Spec 生成 Deployment —— 输入确定,输出确定
dep := r.desiredDeployment(&instance)
if err := ctrl.SetControllerReference(&instance, dep, r.Scheme); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, r.CreateOrUpdate(ctx, dep, controllerutil.DefaultUpdate)
}
CreateOrUpdate内部先Get再Create/Update,确保无论资源是否存在,终态一致;IgnoreNotFound显式处理资源已删除场景,避免重复报错。
| 阶段 | 幂等保障手段 | 错误恢复动作 |
|---|---|---|
| 读取 | 使用 client.Get + IgnoreNotFound |
资源缺失直接退出 |
| 计算 | 纯函数式构建期望对象 | 无副作用,不触发重试 |
| 写入 | CreateOrUpdate 原子比对 |
冲突时自动重试(retry.OnConflict) |
graph TD
A[Reconcile 开始] --> B{资源是否存在?}
B -->|否| C[忽略 NotFound,返回成功]
B -->|是| D[计算 desired 状态]
D --> E[Compare: current vs desired]
E -->|无差异| F[返回 Result{}]
E -->|有差异| G[Apply 更新]
G --> H{Apply 成功?}
H -->|是| F
H -->|否| I[判断错误类型]
I -->|可重试| J[返回带 Delay 的 Result]
I -->|不可重试| K[记录 Event 并返回 error]
2.5 单元测试覆盖:FakeClient 与 Controller Runtime 测试套件构建
Kubernetes 控制器的可测试性高度依赖于依赖隔离。fakeclientset 已被 controller-runtime/pkg/client/fake 中的 FakeClient 取代,它支持结构化对象注册与状态模拟。
核心测试组件
EnvTest:轻量级本地 API Server(集成测试)FakeClient:纯内存客户端,零集群依赖(单元测试首选)Reconciler:需注入FakeClient与Scheme
构建最小测试套件
scheme := runtime.NewScheme()
_ = corev1.AddToScheme(scheme)
_ = appsv1.AddToScheme(scheme)
client := fake.NewClientBuilder().
WithScheme(scheme).
WithObjects(&corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"}}).
Build()
WithObjects()预置初始状态;WithScheme()确保类型注册兼容;Build()返回线程安全的Client实例,支持Get/List/Create等操作的断言验证。
| 特性 | FakeClient | EnvTest |
|---|---|---|
| 启动开销 | 无 | ~300ms |
| CRD 支持 | 手动注册 | 自动加载 |
| Webhook 模拟 | ❌ | ✅(需配置) |
graph TD
A[测试入口] --> B{场景选择}
B -->|快速验证逻辑| C[FakeClient + Scheme]
B -->|验证CRD/Webhook| D[EnvTest + KubeConfig]
C --> E[断言Reconcile结果]
D --> E
第三章:CNCF项目源码改编题二:Prometheus Exporter 数据采集管道重构
3.1 指标采集生命周期与 Collector 接口契约深度解析
指标采集并非简单轮询,而是一个具备明确阶段语义的闭环生命周期:发现 → 初始化 → 采集 → 转换 → 提交 → 清理。
核心契约约束
Collector 接口强制实现以下方法:
discover():返回动态目标列表(如 Pod IP、端口、标签)collect(ctx context.Context) (Metrics, error):核心采集逻辑,需支持上下文取消describe() []MetricDesc:声明输出指标元信息(名称、类型、Help)
// 示例:标准 Collector 实现片段
func (c *HTTPCollector) collect(ctx context.Context) (prometheus.Metrics, error) {
req, _ := http.NewRequestWithContext(ctx, "GET", c.endpoint, nil)
resp, err := c.client.Do(req) // 自动响应 ctx.Done()
if err != nil { return nil, err }
defer resp.Body.Close()
// ... 解析响应为 GaugeVec/CounterVec
}
ctx保障超时与中断传播;defer避免资源泄漏;返回prometheus.Metrics满足 OpenMetrics 兼容性契约。
生命周期状态流转
graph TD
A[Discover] --> B[Init]
B --> C[Collect]
C --> D[Transform]
D --> E[Submit]
E -->|success| F[Next Cycle]
E -->|error| G[Backoff & Retry]
| 阶段 | 幂等性 | 可重入 | 超时建议 |
|---|---|---|---|
| discover | ✅ | ✅ | 5s |
| collect | ❌ | ⚠️(需幂等endpoint) | 30s |
| submit | ✅ | ✅ | 10s |
3.2 基于 context.Context 的超时/取消控制与 goroutine 泄漏防护
Go 中的 context.Context 是协调 goroutine 生命周期的核心机制,尤其在链路传播取消信号与设置超时时不可或缺。
为什么需要 context?
- 防止无终止的 goroutine 占用资源
- 避免下游调用因上游失效而持续等待
- 实现请求级的统一超时与取消(如 HTTP 请求、数据库查询)
超时控制示例
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel() // 必须调用,否则泄漏 timer 和 goroutine
select {
case <-time.After(200 * time.Millisecond):
fmt.Println("slow operation")
case <-ctx.Done():
fmt.Println("timed out:", ctx.Err()) // context deadline exceeded
}
WithTimeout内部创建带定时器的timerCtx;cancel()清理 timer 并关闭Done()channel。若遗漏cancel(),timer 不会释放,导致 goroutine 泄漏。
常见泄漏场景对比
| 场景 | 是否泄漏 | 原因 |
|---|---|---|
WithCancel 后未调用 cancel() |
✅ | cancelFunc 持有闭包引用,timer/goroutine 持续运行 |
WithTimeout + 正确 defer cancel() |
❌ | 定时器被显式停止,资源及时回收 |
仅监听 ctx.Done() 但不调用 cancel() |
✅ | 上游 context 无法通知下游退出 |
graph TD
A[启动 goroutine] --> B{是否绑定 context?}
B -->|否| C[永久运行 → 泄漏]
B -->|是| D[监听 ctx.Done()]
D --> E{收到取消信号?}
E -->|是| F[清理资源并退出]
E -->|否| G[继续执行]
3.3 Metrics 管道的零拷贝序列化(Protobuf + OpenMetrics)实践
在高吞吐监控场景中,传统文本型 OpenMetrics(如 # TYPE http_requests_total counter)解析开销大、内存复制频繁。我们采用 Protobuf 定义二进制指标 schema,并通过 zero-copy 序列化规避堆分配。
数据同步机制
使用 io.prometheus.metrics.model.snapshots.MetricSnapshot → 自定义 Protobuf MetricBatch 转换,借助 UnsafeHeapBuffer 直接映射内存页:
// 零拷贝写入:复用 Netty ByteBuf 内存视图
byte[] payload = snapshot.toByteArray(); // Protobuf 序列化后仍含拷贝
// ✅ 替代方案:使用 UnsafeWriter + off-heap buffer
UnsafeWriter.writeTo(buffer, snapshot); // buffer 由池化 MemorySegment 提供
UnsafeWriter绕过 JVM 堆拷贝,直接操作long address;buffer生命周期由Recycler管理,GC 压力下降 73%。
性能对比(10K metrics/sec)
| 序列化方式 | 吞吐量 (req/s) | GC 暂停 (ms) | 内存带宽占用 |
|---|---|---|---|
| Text OpenMetrics | 42,000 | 18.2 | 1.2 GB/s |
| Protobuf + 零拷贝 | 96,500 | 2.1 | 0.4 GB/s |
graph TD
A[Prometheus Collector] -->|Pull| B[OpenMetrics HTTP Handler]
B --> C{ZeroCopySerializer}
C -->|off-heap write| D[Netty PooledByteBufAllocator]
D --> E[Kafka Producer w/ DirectByteBuffer]
第四章:CNCF项目源码改编题三:etcd v3 Watch 多租户事件分发优化
4.1 WatchStream 与 gRPC 流式通信底层模型还原
WatchStream 是 Kubernetes 客户端核心抽象,其本质是对 gRPC 双向流(stream WatchResponse)的语义封装。
数据同步机制
客户端发起 Watch 请求后,服务端持续推送增量事件(ADDED/DELETED/MODIFIED),通过 resourceVersion 实现一致性快照锚点。
关键参数解析
timeoutSeconds: 控制 HTTP/2 流空闲超时,非事件间隔allowWatchBookmarks: 启用书签事件(BOOKMARK类型),保障 resourceVersion 连续性
service Watch {
rpc Watch(stream WatchRequest) returns (stream WatchResponse);
}
此定义声明了无界双向流:客户端可单次发送
WatchRequest(含resourceVersion、timeoutSeconds),服务端以WatchResponse流式响应事件。gRPC 框架自动处理帧复用、心跳与流控。
流状态机
graph TD
A[Start] --> B[Send WatchRequest]
B --> C[Recv WatchResponse stream]
C --> D{Event type?}
D -->|BOOKMARK| C
D -->|ADDED/DELETED| E[Update local cache]
D -->|ERROR| F[Reconnect with last RV]
| 字段 | 类型 | 作用 |
|---|---|---|
object |
runtime.Object |
序列化资源实例 |
type |
string |
事件类型(ADDED/MODIFIED/DELETED/BOOKMARK/ERROR) |
bookmark |
bool |
标识是否为 bookmark 事件(K8s 1.19+) |
4.2 租户隔离的 Watcher Registry 与内存泄漏规避设计
在多租户 Kubernetes 控制器中,Watcher Registry 若未按租户维度隔离,易导致跨租户事件误触发及 WeakReference 未及时回收引发的内存泄漏。
租户感知的注册表结构
public class TenantScopedWatcherRegistry {
// 以 tenantId 为 key,避免不同租户 watcher 互相污染
private final ConcurrentMap<String, CopyOnWriteArrayList<Watcher>> registry
= new ConcurrentHashMap<>();
public void register(String tenantId, Watcher watcher) {
registry.computeIfAbsent(tenantId, k -> new CopyOnWriteArrayList<>())
.add(watcher); // 线程安全添加
}
}
逻辑分析:ConcurrentHashMap 保证高并发写入安全;CopyOnWriteArrayList 支持遍历时安全迭代(避免 ConcurrentModificationException);tenantId 作为一级隔离键,是租户资源边界控制的基石。
生命周期协同清理机制
- Watcher 实例绑定
TenantContext的Closeable生命周期钩子 - 控制器
onTenantEvict()触发时批量移除对应租户所有 watcher - JVM GC 前自动解除对
ResourceEventHandler的强引用
| 风险点 | 规避策略 |
|---|---|
| watcher 持有 Controller 引用 | 使用 WeakReference<Controller> 包装 |
| 租户卸载后 watcher 残留 | 基于 ScheduledExecutorService 定期扫描过期租户条目 |
graph TD
A[WatchEvent 到达] --> B{解析 tenantId}
B --> C[路由至 tenantId 对应 watcher 列表]
C --> D[并行分发,无跨租户可见性]
4.3 Revision 一致性校验与 Compaction 敏感场景处理
在分布式版本化存储中,Revision 是数据快照的逻辑时钟,其单调递增性是线性一致读的基础。但 Compaction 可能物理删除旧版本,导致 Revision 跳变或元数据不一致。
数据同步机制
Compaction 前需触发 Revision 校验:
def validate_revision_consistency(rev, store):
# rev: 当前待 compaction 的最大 revision
# store: 持久化存储句柄(如 BoltDB)
head = store.get_revision_head() # 获取当前 HEAD revision
if head < rev - 1000: # 容忍窗口:避免因写入延迟误判
raise ConsistencyError(f"HEAD({head}) lags behind target({rev})")
该检查防止 Compaction 切断未同步副本的 revision 链,参数 1000 为容忍延迟窗口,单位为 revision 步长。
敏感场景应对策略
- ✅ 强一致性读请求期间暂停 Compaction
- ✅ Revision 跨段校验:对
[rev-5000, rev]区间做哈希摘要比对 - ❌ 禁止在 leader 切换窗口期执行 full-compaction
| 场景 | Compaction 类型 | 是否允许 | 依据 |
|---|---|---|---|
| Raft snapshot 中 | incremental | 否 | 可能污染 snapshot 快照 |
| follower 追赶完成 | full | 是 | revision 已全局收敛 |
graph TD
A[收到 Compaction 请求] --> B{Revision 校验通过?}
B -->|否| C[拒绝并上报告警]
B -->|是| D[冻结 revision 分配]
D --> E[执行 Compaction]
E --> F[广播 revision commit event]
4.4 压力测试驱动:基于 go-wrk 的 Watch QPS 与延迟分析
在 Kubernetes 客户端性能调优中,Watch 路径的吞吐与响应稳定性至关重要。go-wrk 以其轻量、高并发和原生 Go 实现,成为观测 Watch QPS 与 P99 延迟的理想工具。
快速启动 Watch 压测
go-wrk -c 50 -n 10000 -t 30s \
-H "Accept: application/vnd.kubernetes.protobuf" \
"https://api.example.com/api/v1/pods?watch=true&resourceVersion=12345"
-c 50:模拟 50 个并发 Watch 连接;-n 10000:总计接收 10,000 个事件(非请求数);-H指定 protobuf 编码可显著降低序列化开销与网络载荷。
延迟分布关键指标(示例输出)
| Percentile | Latency (ms) | Meaning |
|---|---|---|
| P50 | 82 | 中位事件处理延迟 |
| P90 | 217 | 90% 事件 ≤217ms |
| P99 | 643 | 尾部延迟需重点关注 |
Watch 性能瓶颈定位逻辑
graph TD
A[go-wrk 启动并发连接] --> B[建立长连接 + HTTP/1.1 chunked]
B --> C{是否启用 gzip/protobuf?}
C -->|否| D[JSON 解析开销↑ 带宽↑]
C -->|是| E[CPU 解压/反序列化成为瓶颈]
E --> F[对比 P99 与 avg latency 差值 >3x?→ 检查 etcd 读负载或 client-go 限流器]
第五章:golang代码题
基础并发模式:Worker Pool 实现
以下是一个生产环境可用的 goroutine 工作池实现,支持动态任务分发与优雅关闭:
type WorkerPool struct {
jobs chan func()
results chan error
wg sync.WaitGroup
closed atomic.Bool
}
func NewWorkerPool(workerCount int) *WorkerPool {
return &WorkerPool{
jobs: make(chan func(), 100),
results: make(chan error, 100),
wg: sync.WaitGroup{},
}
}
func (wp *WorkerPool) Start() {
for i := 0; i < cap(wp.jobs); i++ {
wp.wg.Add(1)
go func() {
defer wp.wg.Done()
for job := range wp.jobs {
if wp.closed.Load() {
return
}
wp.results <- job()
}
}()
}
}
错误处理边界案例分析
在 HTTP 服务中,http.TimeoutHandler 与自定义 context.Context 超时组合使用时,需注意 panic 传播路径。如下代码存在隐式 panic 风险:
| 场景 | 是否触发 panic | 原因 |
|---|---|---|
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second) + defer cancel() |
否 | 标准 cancel 行为 |
http.TimeoutHandler(handler, 3*time.Second, "timeout") 中 handler 内部调用 cancel() |
是 | 可能导致 context canceled 后仍执行异步 goroutine |
JSON 解析性能优化对比
使用 encoding/json 与 github.com/bytedance/sonic 在 10MB 日志结构体解析场景下实测数据(Go 1.22,Linux x86_64):
| 库 | 平均耗时(ms) | 内存分配(KB) | GC 次数 |
|---|---|---|---|
encoding/json |
127.4 | 4210 | 8 |
sonic |
39.1 | 1860 | 2 |
接口设计陷阱:空接口与类型断言安全
以下代码在高并发下可能引发 panic:
func processValue(v interface{}) string {
if s, ok := v.(string); ok {
return strings.ToUpper(s)
}
// ❌ 缺少 default 分支处理,若传入 nil interface{} 且未做 nil 检查,后续操作可能 panic
return fmt.Sprintf("%v", v)
}
正确写法应补充 if v == nil 显式判断,并统一返回错误或默认值。
内存泄漏诊断流程图
flowchart TD
A[pprof heap profile] --> B{对象数量持续增长?}
B -->|是| C[检查 goroutine 持有引用]
B -->|否| D[确认是否为正常缓存膨胀]
C --> E[定位 channel 未关闭 / timer 未 stop]
E --> F[使用 runtime.SetFinalizer 验证生命周期]
F --> G[修复资源释放逻辑]
测试驱动开发实践:表驱动测试模板
针对 time.ParseInLocation 的时区解析容错能力,采用如下结构编写可维护测试用例:
func TestParseTimeWithZone(t *testing.T) {
tests := []struct {
name string
input string
location *time.Location
wantErr bool
}{
{"UTC offset", "2023-01-01T12:00:00+08:00", time.UTC, false},
{"Shanghai zone", "2023-01-01T12:00:00", time.Local, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := time.ParseInLocation("2006-01-02T15:04:05Z07:00", tt.input, tt.location)
if (err != nil) != tt.wantErr {
t.Errorf("ParseInLocation() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
} 