Compare commits

...

4 Commits

Author SHA1 Message Date
e484aaa1f9 add synchelper; 2023-04-07 22:41:19 +08:00
ff69d51e50 add runtimehelper; 2023-04-07 21:54:51 +08:00
521fee9817 add wireexample; 2023-03-31 14:07:56 +08:00
d98d6dffef add contexthelper; 2023-03-29 18:11:01 +08:00
14 changed files with 426 additions and 0 deletions

3
contexthelper/go.mod Normal file
View File

@@ -0,0 +1,3 @@
module git.blauwelle.com/go/crate/contexthelper
go 1.20

17
contexthelper/nocancel.go Normal file
View File

@@ -0,0 +1,17 @@
package contexthelper
import "context"
// WithNoCancel 阻断上级 ctx 的中断信号
// 想要使用上级 ctx 中的值但不想受上级 ctx 控制退出时可以使用
func WithNoCancel(ctx context.Context) context.Context {
return &noCancelCtx{Context: ctx}
}
type noCancelCtx struct {
context.Context
}
func (ctx *noCancelCtx) Done() <-chan struct{} {
return nil
}

3
runtimehelper/go.mod Normal file
View File

@@ -0,0 +1,3 @@
module git.blauwelle.com/go/crate/runtimehelper
go 1.20

162
runtimehelper/helper.go Normal file
View File

@@ -0,0 +1,162 @@
package runtimehelper
import (
"fmt"
"path/filepath"
"runtime"
)
// Frame 调用相关信息
type Frame struct {
Function string `json:"func"`
File string `json:"file"`
Line int `json:"line"`
}
// IsValid 表示是否有效
func (frame Frame) IsValid() bool {
return frame.Line > 0
}
// GetFilename 返回文件名
func (frame Frame) GetFilename() string {
_, file := filepath.Split(frame.File)
return file
}
// SplitFilePath 返回 File 对应的路径和文件名
func (frame Frame) SplitFilePath() (string, string) {
return filepath.Split(frame.File)
}
// SplitFunction 返回 Function 对应的包路径和函数名
func (frame Frame) SplitFunction() (string, string) {
return PackagePathQualifiedFunctionName(frame.Function).Split()
}
// Caller 返回当前调用对应的 Frame,
// skip=0 表示调用 Caller 处.
func Caller(skip int) Frame {
pc := make([]uintptr, 1)
n := runtime.Callers(skip+2, pc)
frame, _ := runtime.CallersFrames(pc[:n]).Next()
if frame.PC == 0 {
return Frame{}
}
return Frame{
Function: frame.Function,
File: frame.File,
Line: frame.Line,
}
}
// Stack 返回调用栈,
// skip=0 表示调用 Stack 处.
func Stack(skip, maximumFrames int) []Frame {
pc := make([]uintptr, maximumFrames)
n := runtime.Callers(skip+2, pc)
stack := make([]Frame, 0, n)
frames := runtime.CallersFrames(pc[:n])
for {
frame, more := frames.Next()
if frame.PC != 0 {
stack = append(stack, Frame{
Function: frame.Function,
File: frame.File,
Line: frame.Line,
})
}
if !more {
break
}
}
return stack
}
// CallerFrame 函数调用对应的 [runtime.Frame],
// 从 [runtime.Frame] 中可以获取调用路径上的函数的名字、文件、行号等信息;
// skip 值对应的 [runtime.Frame]:
// - -1: CallerFrame
// - 0: 调用 CallerFrame 处
//
// 可以使用 [runtime.Frame.PC] != 0 判断 runtime.Frame 有效
func CallerFrame(skip int) runtime.Frame {
pc := make([]uintptr, 1)
n := runtime.Callers(skip+2, pc)
frame, _ := runtime.CallersFrames(pc[:n]).Next()
return frame
}
// CallersFrames 返回函数调用对应的 [runtime.Frames],
// 从 Frames 中可以获取调用路径上的函数的名字、文件、行号等信息;
// skip 值对应的第1个 [runtime.Frame]:
// - -2: [runtime.Callers]
// - -1: CallersFrames
// - 0: 调用 CallersFrames 处
func CallersFrames(skip, maximumFrames int) *runtime.Frames {
pc := make([]uintptr, maximumFrames)
n := runtime.Callers(skip+2, pc)
return runtime.CallersFrames(pc[:n])
}
// PrintCallersFrames 打印函数调用栈,
// 从调用 PrintCallersFrames 的地方开始打印
func PrintCallersFrames() {
frames := CallersFrames(3, 32)
for {
frame, more := frames.Next()
if frame.PC != 0 {
fmt.Printf("%s:%d in %s\n", frame.File, frame.Line, frame.Function)
}
if !more {
break
}
}
}
// PackagePathQualifiedFunctionName 是 [runtime.Frame.Function] (package path-qualified function name)
type PackagePathQualifiedFunctionName string
// Split 把完整的函数路径切割成包路径和函数名
func (f PackagePathQualifiedFunctionName) Split() (string, string) {
var period int
loop:
for i := len(f) - 1; i >= 0; i-- {
switch f[i] {
case '/':
break loop
case '.':
period = i
}
}
if period == 0 {
return "", ""
}
return string(f[:period]), string(f[period+1:])
}
// PackageName 把完整的函数路径切割成包路径和函数名
func (f PackagePathQualifiedFunctionName) PackageName() string {
var period int
for i := len(f) - 1; i >= 0; i-- {
switch f[i] {
case '/':
return string(f[:period])
case '.':
period = i
}
}
return string(f[:period])
}
// SplitPackageFunctionName 把完整的函数路径切割成包路径和函数名
// 参数 f 是 [runtime.Frame.Function] (package path-qualified function name),
func SplitPackageFunctionName(f string) (string, string) {
return PackagePathQualifiedFunctionName(f).Split()
}
// GetPackageName 把完整的函数路径切割成包路径和函数名
// 参数 f 是 [runtime.Frame.Function] (package path-qualified function name),
func GetPackageName(f string) string {
return PackagePathQualifiedFunctionName(f).PackageName()
}

View File

@@ -0,0 +1,18 @@
package synchelper
import "bytes"
// NewBytesBufferPool 返回新的 BytesBufferPool
func NewBytesBufferPool(initialSize, maximumSize int) BytesBufferPool {
return NewPool(
func() any {
return bytes.NewBuffer(make([]byte, 0, initialSize))
},
func(v *bytes.Buffer) bool {
return v.Cap() <= maximumSize
},
)
}
// BytesBufferPool 是 [*bytes.Buffer] 的资源池
type BytesBufferPool = Pool[*bytes.Buffer]

3
synchelper/go.mod Normal file
View File

@@ -0,0 +1,3 @@
module git.blauwelle.com/go/crate/synchelper
go 1.20

25
synchelper/io_writer.go Normal file
View File

@@ -0,0 +1,25 @@
package synchelper
import (
"io"
"sync"
)
// NewSyncWriter 返回写互斥的 writer
func NewSyncWriter(writer io.Writer) io.Writer {
return &syncWriter{
writer: writer,
lock: sync.Mutex{},
}
}
type syncWriter struct {
writer io.Writer
lock sync.Mutex
}
func (w *syncWriter) Write(p []byte) (n int, err error) {
w.lock.Lock()
defer w.lock.Unlock()
return w.writer.Write(p)
}

34
synchelper/pool.go Normal file
View File

@@ -0,0 +1,34 @@
package synchelper
import (
"sync"
)
// NewPool 初始化 Pool,
// newFn 是资源的构造函数,
// putCond 返回 true 时表示资源可以被放回 Pool 中.
func NewPool[T any](newFn func() any, putCond func(v T) bool) Pool[T] {
return Pool[T]{
pool: &sync.Pool{New: newFn},
putCond: putCond,
}
}
// Pool 是通用的资源池
type Pool[T any] struct {
pool *sync.Pool
putCond func(v T) bool
}
// Get 获取资源
func (pool Pool[T]) Get() T {
return pool.pool.Get().(T)
}
// Put 放回资源
func (pool Pool[T]) Put(v T) {
if !pool.putCond(v) {
return
}
pool.pool.Put(v)
}

View File

@@ -0,0 +1,29 @@
// 展示 wire 的两种 mock 实现
package main
import "fmt"
func main() {
approachA()
approachB()
}
// approach A:
// - 需要 mock 的对象定义成接口, 真正的实现和 mock 分别实现接口;
// - injector 函数使用 接口类型参数, 测试时传入 mock 对象;
func approachA() {
rander := &MockRander{}
guess := InjectGuessWithoutMock(rander)
rander.Value = 1
fmt.Println("approachA:", guess.Guess(10))
}
// approach B:
// - mock 和 真正的实现分别定义到两组 injector 中, injector 提供1个对象包含要测试的对象和 mock;
// - 测试时调用 mock injector 返回对象(包含要测试的对象和 mock);
func approachB() {
guessWithMock := InjectMockGuess()
guessWithMock.Mock.Value = 1
fmt.Println("approachB:", guessWithMock.Guess.Guess(10))
}

View File

@@ -0,0 +1,64 @@
package main
import (
"fmt"
"math/rand"
"github.com/google/wire"
)
type Rander interface {
Rand() int
}
type DefaultRander struct{}
func (r *DefaultRander) Rand() int {
return rand.Int()
}
func NewRander() *DefaultRander {
return &DefaultRander{}
}
type MockRander struct {
Value int
}
func (r *MockRander) Rand() int {
return r.Value
}
type Guess struct {
rander Rander
}
func (g *Guess) Guess(guess int) string {
return fmt.Sprintf("you guess %d, real number is %d", guess, g.rander.Rand())
}
func NewGuess(rander Rander) *Guess {
return &Guess{
rander: rander,
}
}
type GuessWithMock struct {
Guess *Guess
Mock *MockRander
}
var GuessProviderSetWithoutMock = wire.NewSet(NewGuess)
var GuessProviderSet = wire.NewSet(
NewRander,
NewGuess,
wire.Bind(new(Rander), new(*DefaultRander)),
)
var MockGuessProviderSet = wire.NewSet(
wire.Struct(new(MockRander)),
NewGuess,
wire.Bind(new(Rander), new(*MockRander)),
wire.Struct(new(GuessWithMock), "*"),
)

View File

@@ -0,0 +1,20 @@
//go:build wireinject
package main
import "github.com/google/wire"
// Approach A
func InjectGuessWithoutMock(rander Rander) *Guess {
panic(wire.Build(GuessProviderSetWithoutMock))
}
// Approach B
func InjectRealGuess() *Guess {
panic(wire.Build(GuessProviderSet))
}
// Approach B
func InjectMockGuess() *GuessWithMock {
panic(wire.Build(MockGuessProviderSet))
}

View File

@@ -0,0 +1,33 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject
package main
// Injectors from wire.go:
// Approach A
func InjectGuessWithoutMock(rander Rander) *Guess {
guess := NewGuess(rander)
return guess
}
// Approach B
func InjectRealGuess() *Guess {
defaultRander := NewRander()
guess := NewGuess(defaultRander)
return guess
}
// Approach B
func InjectMockGuess() *GuessWithMock {
mockRander := &MockRander{}
guess := NewGuess(mockRander)
guessWithMock := &GuessWithMock{
Guess: guess,
Mock: mockRander,
}
return guessWithMock
}

5
wireexample/go.mod Normal file
View File

@@ -0,0 +1,5 @@
module git.blauwelle.com/go/crate/wireexample
go 1.20
require github.com/google/wire v0.5.0

10
wireexample/go.sum Normal file
View File

@@ -0,0 +1,10 @@
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8=
github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=