This commit is contained in:
25
.drone.yml
Normal file
25
.drone.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
kind: pipeline
|
||||||
|
name: default
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: test
|
||||||
|
image: golang
|
||||||
|
commands:
|
||||||
|
- go test -parallel 10 --race ./...
|
||||||
|
|
||||||
|
- name: golangci-lint
|
||||||
|
image: golangci/golangci-lint:v1.39
|
||||||
|
commands:
|
||||||
|
- golangci-lint run
|
||||||
|
|
||||||
|
- name: test routine
|
||||||
|
image: golang
|
||||||
|
commands:
|
||||||
|
- cd routine
|
||||||
|
- go test -parallel 10 --race ./...
|
||||||
|
|
||||||
|
- name: golangci-lint routine
|
||||||
|
image: golangci/golangci-lint:v1.39
|
||||||
|
commands:
|
||||||
|
- cd routine
|
||||||
|
- golangci-lint run
|
||||||
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# ---> Go
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
46
.golangci.yml
Normal file
46
.golangci.yml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
run:
|
||||||
|
timeout: 5m
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
dupl:
|
||||||
|
threshold: 100
|
||||||
|
funlen:
|
||||||
|
lines: 100
|
||||||
|
statements: 50
|
||||||
|
goconst:
|
||||||
|
min-len: 2
|
||||||
|
min-occurrences: 2
|
||||||
|
gocyclo:
|
||||||
|
min-complexity: 15
|
||||||
|
golint:
|
||||||
|
min-confidence: 0
|
||||||
|
govet:
|
||||||
|
check-shadowing: true
|
||||||
|
lll:
|
||||||
|
line-length: 140
|
||||||
|
maligned:
|
||||||
|
suggest-new: true
|
||||||
|
misspell:
|
||||||
|
locale: US
|
||||||
|
|
||||||
|
linters:
|
||||||
|
enable-all: true
|
||||||
|
disable:
|
||||||
|
- exhaustivestruct
|
||||||
|
- maligned
|
||||||
|
- interfacer
|
||||||
|
- scopelint
|
||||||
|
|
||||||
|
issues:
|
||||||
|
# Excluding configuration per-path, per-linter, per-text and per-source
|
||||||
|
exclude-rules:
|
||||||
|
- path: _test\.go
|
||||||
|
linters:
|
||||||
|
- gomnd
|
||||||
|
- exhaustivestruct
|
||||||
|
- wrapcheck
|
||||||
|
- path: test/*
|
||||||
|
linters:
|
||||||
|
- gomnd
|
||||||
|
- exhaustivestruct
|
||||||
|
- wrapcheck
|
||||||
19
LICENSE
Normal file
19
LICENSE
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
MIT License Copyright (c) 2020 4devs
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice (including the next
|
||||||
|
paragraph) shall be included in all copies or substantial portions of the
|
||||||
|
Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
||||||
|
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
||||||
|
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
5
README.md
Normal file
5
README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# closer
|
||||||
|
|
||||||
|
[](https://drone.gitoa.ru/go-4devs/closer)
|
||||||
|
[](https://goreportcard.com/report/gitoa.ru/go-4devs/closer)
|
||||||
|
[](http://godoc.org/gitoa.ru/go-4devs/closer)
|
||||||
109
async/closer.go
Normal file
109
async/closer.go
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package async
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Option configure async closer.
|
||||||
|
type Option func(*Closer)
|
||||||
|
|
||||||
|
// WithHandleError configure async error handler.
|
||||||
|
func WithHandleError(he func(error)) Option {
|
||||||
|
return func(async *Closer) {
|
||||||
|
async.handler = he
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New create new closer with options.
|
||||||
|
func New(opts ...Option) *Closer {
|
||||||
|
a := &Closer{}
|
||||||
|
|
||||||
|
for _, o := range opts {
|
||||||
|
o(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// Closer closer.
|
||||||
|
type Closer struct {
|
||||||
|
sync.Mutex
|
||||||
|
once sync.Once
|
||||||
|
done chan struct{}
|
||||||
|
fnc []func() error
|
||||||
|
handler func(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait when done context or notify signals or close all.
|
||||||
|
func (c *Closer) Wait(ctx context.Context, sig ...os.Signal) {
|
||||||
|
go func() {
|
||||||
|
ch := make(chan os.Signal, 1)
|
||||||
|
|
||||||
|
if len(sig) > 0 {
|
||||||
|
signal.Notify(ch, sig...)
|
||||||
|
defer signal.Stop(ch)
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ch:
|
||||||
|
case <-ctx.Done():
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = c.Close()
|
||||||
|
}()
|
||||||
|
<-c.wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Closer) wait() chan struct{} {
|
||||||
|
c.Lock()
|
||||||
|
if c.done == nil {
|
||||||
|
c.done = make(chan struct{})
|
||||||
|
}
|
||||||
|
c.Unlock()
|
||||||
|
|
||||||
|
return c.done
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add close functions.
|
||||||
|
func (c *Closer) Add(f ...func() error) {
|
||||||
|
c.Lock()
|
||||||
|
c.fnc = append(c.fnc, f...)
|
||||||
|
c.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close close all closers async.
|
||||||
|
func (c *Closer) Close() error {
|
||||||
|
c.once.Do(func() {
|
||||||
|
defer close(c.wait())
|
||||||
|
c.Lock()
|
||||||
|
eh := func(error) {}
|
||||||
|
if c.handler != nil {
|
||||||
|
eh = c.handler
|
||||||
|
}
|
||||||
|
funcs := c.fnc
|
||||||
|
c.fnc = nil
|
||||||
|
c.Unlock()
|
||||||
|
|
||||||
|
errs := make(chan error, len(funcs))
|
||||||
|
for _, f := range funcs {
|
||||||
|
go func(f func() error) {
|
||||||
|
errs <- f()
|
||||||
|
}(f)
|
||||||
|
}
|
||||||
|
for i := 0; i < cap(errs); i++ {
|
||||||
|
if err := <-errs; err != nil {
|
||||||
|
eh(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Closer) SetErrHandler(e func(error)) {
|
||||||
|
c.Lock()
|
||||||
|
c.handler = e
|
||||||
|
c.Unlock()
|
||||||
|
}
|
||||||
70
async/closer_example_test.go
Normal file
70
async/closer_example_test.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package async_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/closer/async"
|
||||||
|
"gitoa.ru/go-4devs/closer/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleCloser_Close() {
|
||||||
|
cl := async.New()
|
||||||
|
|
||||||
|
cl.Add(func() error {
|
||||||
|
fmt.Print("do some close")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
cl.Close()
|
||||||
|
// Output: do some close
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleCloser_Wait_cancelContext() {
|
||||||
|
cl := async.New()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.TODO(), time.Microsecond)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
cl.Add(func() error {
|
||||||
|
fmt.Print("do some close with cancel context")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
cl.Wait(ctx)
|
||||||
|
// Output: do some close with cancel context
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleWithHandleError() {
|
||||||
|
cl := async.New(async.WithHandleError(func(err error) {
|
||||||
|
fmt.Printf("logged err:%s", err)
|
||||||
|
}))
|
||||||
|
|
||||||
|
cl.Add(func() error {
|
||||||
|
return test.ErrClose
|
||||||
|
})
|
||||||
|
|
||||||
|
cl.Close()
|
||||||
|
// Output: logged err:some error
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleCloser_Wait_syscall() {
|
||||||
|
time.AfterFunc(time.Millisecond, func() {
|
||||||
|
_ = syscall.Kill(syscall.Getpid(), syscall.SIGTERM)
|
||||||
|
})
|
||||||
|
|
||||||
|
cl := async.New()
|
||||||
|
|
||||||
|
cl.Add(func() error {
|
||||||
|
fmt.Print("do some close with SIGTERM")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
cl.Wait(context.TODO(), syscall.SIGTERM)
|
||||||
|
// Output: do some close with SIGTERM
|
||||||
|
}
|
||||||
95
async/closer_test.go
Normal file
95
async/closer_test.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package async_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"sync/atomic"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/closer/async"
|
||||||
|
"gitoa.ru/go-4devs/closer/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAsyncClose(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
c := async.Closer{}
|
||||||
|
closedFn := &test.Closed{}
|
||||||
|
c.Add(closedFn.CloseFnc("one", 0))
|
||||||
|
test.RequireNil(t, c.Close())
|
||||||
|
closedFn.TestLen(t, 1)
|
||||||
|
|
||||||
|
c.Wait(context.Background())
|
||||||
|
test.RequireNil(t, c.Close())
|
||||||
|
closedFn.TestLen(t, 1)
|
||||||
|
|
||||||
|
as := async.New(async.WithHandleError(func(e error) {
|
||||||
|
if !errors.Is(e, test.ErrClose) {
|
||||||
|
t.Fatalf("expect: %s, got:%s", test.ErrClose, e)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
as.Add(closedFn.CloseFnc("two", 0), func() error {
|
||||||
|
test.RequireNil(t, closedFn.CloseFnc("two", 0)())
|
||||||
|
|
||||||
|
return test.ErrClose
|
||||||
|
})
|
||||||
|
test.RequireNil(t, as.Close())
|
||||||
|
closedFn.TestLen(t, 3)
|
||||||
|
|
||||||
|
c = async.Closer{}
|
||||||
|
closedFn = &test.Closed{}
|
||||||
|
c.Add(
|
||||||
|
closedFn.CloseFnc("one", time.Millisecond/2),
|
||||||
|
closedFn.CloseFnc("two", time.Microsecond),
|
||||||
|
closedFn.CloseFnc("three", time.Millisecond/5))
|
||||||
|
test.RequireNil(t, c.Close())
|
||||||
|
|
||||||
|
closedFn.TestLen(t, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAsyncWait_Timeout(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
c := &async.Closer{}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Microsecond)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var cnt int32
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
c.Wait(ctx)
|
||||||
|
atomic.AddInt32(&cnt, 1)
|
||||||
|
}()
|
||||||
|
|
||||||
|
cl := func() error {
|
||||||
|
atomic.AddInt32(&cnt, 1)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Add(cl)
|
||||||
|
c.Wait(context.Background())
|
||||||
|
|
||||||
|
if atomic.LoadInt32(&cnt) != 1 {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAsyncWait_Syscall(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
c := async.New()
|
||||||
|
cl := &test.Closed{}
|
||||||
|
|
||||||
|
time.AfterFunc(time.Second, func() {
|
||||||
|
test.RequireNil(t, syscall.Kill(syscall.Getpid(), syscall.SIGTERM))
|
||||||
|
})
|
||||||
|
|
||||||
|
c.Add(cl.CloseFnc("one", 0), cl.CloseFnc("one", 0))
|
||||||
|
c.Wait(context.Background(), syscall.SIGTERM, syscall.SIGINT)
|
||||||
|
|
||||||
|
cl.TestLen(t, 2)
|
||||||
|
}
|
||||||
53
closer.go
Normal file
53
closer.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package closer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/closer/priority"
|
||||||
|
)
|
||||||
|
|
||||||
|
// nolint: gochecknoglobals
|
||||||
|
var closer = &priority.Closer{}
|
||||||
|
|
||||||
|
// SetTimeout before close func.
|
||||||
|
func SetTimeout(t time.Duration) {
|
||||||
|
closer.SetTimeout(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetErrHandler before close func.
|
||||||
|
func SetErrHandler(eh func(error)) {
|
||||||
|
closer.SetErrHandler(eh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add add closed func.
|
||||||
|
func Add(f ...func() error) {
|
||||||
|
closer.Add(f...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddByPriority add close by priority 255 its close first 0 - last.
|
||||||
|
func AddByPriority(priority uint8, f ...func() error) {
|
||||||
|
closer.AddByPriority(priority, f...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddLast add closer which execute at the end.
|
||||||
|
func AddLast(f ...func() error) {
|
||||||
|
closer.AddLast(f...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFirst add closer which execute at the begin.
|
||||||
|
func AddFirst(f ...func() error) {
|
||||||
|
closer.AddFirst(f...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close all func.
|
||||||
|
// nolint: wrapcheck
|
||||||
|
func Close() error {
|
||||||
|
return closer.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait cancel ctx or signal.
|
||||||
|
func Wait(ctx context.Context, sig ...os.Signal) {
|
||||||
|
closer.Wait(ctx, sig...)
|
||||||
|
}
|
||||||
125
closer_example_test.go
Normal file
125
closer_example_test.go
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package closer_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/closer"
|
||||||
|
"gitoa.ru/go-4devs/closer/priority"
|
||||||
|
"gitoa.ru/go-4devs/closer/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleWait() {
|
||||||
|
ctx, cancel := context.WithTimeout(context.TODO(), time.Microsecond)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
closer.SetErrHandler(func(err error) {
|
||||||
|
fmt.Print("\nlogged err:", err.Error())
|
||||||
|
})
|
||||||
|
|
||||||
|
closer.Add(func() error {
|
||||||
|
fmt.Print("do some close. ")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
closer.AddFirst(func() error {
|
||||||
|
fmt.Print("close http server for example. ")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
closer.AddLast(func() error {
|
||||||
|
fmt.Print("close db for example.")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
closer.AddByPriority(priority.Last-1, func() error {
|
||||||
|
return test.ErrClose
|
||||||
|
})
|
||||||
|
|
||||||
|
closer.Wait(ctx)
|
||||||
|
// Output:
|
||||||
|
// close http server for example. do some close. close db for example.
|
||||||
|
// logged err:some error
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleClose() {
|
||||||
|
closer.Add(func() error {
|
||||||
|
// normal stop.
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}, func() error {
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
// long normal stop.
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
closer.AddFirst(func() error {
|
||||||
|
// first stop.
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
closer.AddLast(func() error {
|
||||||
|
// last stop.
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
closer.AddByPriority(priority.First+1, func() error {
|
||||||
|
// run before first.
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
closer.AddByPriority(priority.Normal-1, func() error {
|
||||||
|
// run after normal.
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
closer.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleWait_cancelContext() {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Microsecond)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
closer.Add(func() error {
|
||||||
|
// do some close with cancel context
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
closer.Wait(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleSetErrHandler() {
|
||||||
|
closer.SetErrHandler(func(err error) {
|
||||||
|
log.Print("logged err:", err.Error())
|
||||||
|
})
|
||||||
|
|
||||||
|
closer.Add(func() error {
|
||||||
|
return test.ErrClose
|
||||||
|
})
|
||||||
|
|
||||||
|
closer.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleWait_syscall() {
|
||||||
|
time.AfterFunc(time.Millisecond, func() {
|
||||||
|
_ = syscall.Kill(syscall.Getpid(), syscall.SIGTERM)
|
||||||
|
})
|
||||||
|
|
||||||
|
closer.Add(func() error {
|
||||||
|
// do some close with SIGTERM
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
closer.Wait(context.TODO(), syscall.SIGTERM)
|
||||||
|
}
|
||||||
220
priority/closer.go
Normal file
220
priority/closer.go
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
package priority
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// defaults.
|
||||||
|
const (
|
||||||
|
First = 250
|
||||||
|
Normal = 100
|
||||||
|
Last = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
// Option configure priority closer.
|
||||||
|
type Option func(*Closer)
|
||||||
|
|
||||||
|
// WithHandleError configure priority error handler.
|
||||||
|
func WithHandleError(he func(error)) Option {
|
||||||
|
return func(async *Closer) {
|
||||||
|
async.handler = he
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTimeout configure priority error handler.
|
||||||
|
func WithTimeout(d time.Duration) Option {
|
||||||
|
return func(async *Closer) {
|
||||||
|
async.timeout = d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New create new closer with options.
|
||||||
|
func New(opts ...Option) *Closer {
|
||||||
|
a := &Closer{}
|
||||||
|
|
||||||
|
for _, o := range opts {
|
||||||
|
o(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// Closer close by priority.
|
||||||
|
type Closer struct {
|
||||||
|
sync.Mutex
|
||||||
|
fnc map[uint8][]func() error
|
||||||
|
priority prioritySlice
|
||||||
|
sumPriority uint
|
||||||
|
once sync.Once
|
||||||
|
handler func(error)
|
||||||
|
timeout time.Duration
|
||||||
|
len int
|
||||||
|
done chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds closed func by normal priority.
|
||||||
|
func (c *Closer) Add(f ...func() error) {
|
||||||
|
c.AddByPriority(Normal, f...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddLast add closer which execute at the end.
|
||||||
|
func (c *Closer) AddLast(f ...func() error) {
|
||||||
|
c.AddByPriority(Last, f...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFirst add closer which execute at the begin.
|
||||||
|
func (c *Closer) AddFirst(f ...func() error) {
|
||||||
|
c.AddByPriority(First, f...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddByPriority add close by priority 255 its close first 0 - last.
|
||||||
|
func (c *Closer) AddByPriority(priority uint8, f ...func() error) {
|
||||||
|
if len(f) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Lock()
|
||||||
|
if c.fnc == nil {
|
||||||
|
c.fnc = make(map[uint8][]func() error)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.fnc[priority]) == 0 {
|
||||||
|
c.priority = append(c.priority, priority)
|
||||||
|
sort.Sort(c.priority)
|
||||||
|
c.sumPriority += uint(priority)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.len += len(f)
|
||||||
|
c.fnc[priority] = append(c.fnc[priority], f...)
|
||||||
|
c.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait wait signal or cancel context.
|
||||||
|
func (c *Closer) Wait(ctx context.Context, sig ...os.Signal) {
|
||||||
|
go func() {
|
||||||
|
ch := make(chan os.Signal, 1)
|
||||||
|
|
||||||
|
if len(sig) > 0 {
|
||||||
|
signal.Notify(ch, sig...)
|
||||||
|
defer signal.Stop(ch)
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ch:
|
||||||
|
case <-ctx.Done():
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = c.Close()
|
||||||
|
}()
|
||||||
|
<-c.wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes all func by priority.
|
||||||
|
func (c *Closer) Close() error {
|
||||||
|
c.once.Do(func() {
|
||||||
|
start := time.Now()
|
||||||
|
c.Lock()
|
||||||
|
eh := func(err error) {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.handler != nil {
|
||||||
|
eh = c.handler
|
||||||
|
}
|
||||||
|
|
||||||
|
w := &wait{
|
||||||
|
timeout: c.timeout,
|
||||||
|
priority: c.sumPriority,
|
||||||
|
}
|
||||||
|
funcs := c.fnc
|
||||||
|
c.fnc = nil
|
||||||
|
c.Unlock()
|
||||||
|
errs := make(chan error, c.len)
|
||||||
|
go func() {
|
||||||
|
defer close(c.wait())
|
||||||
|
for i := 0; i < cap(errs); i++ {
|
||||||
|
if err := <-errs; err != nil {
|
||||||
|
eh(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for _, p := range c.priority {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(len(funcs[p]))
|
||||||
|
for _, f := range funcs[p] {
|
||||||
|
go func(f func() error) {
|
||||||
|
errs <- f()
|
||||||
|
wg.Done()
|
||||||
|
}(f)
|
||||||
|
}
|
||||||
|
w.done(start, uint(p), &wg)
|
||||||
|
}
|
||||||
|
<-c.wait()
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTimeout before close func.
|
||||||
|
func (c *Closer) SetTimeout(t time.Duration) {
|
||||||
|
c.Lock()
|
||||||
|
c.timeout = t
|
||||||
|
c.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetErrHandler before close func.
|
||||||
|
func (c *Closer) SetErrHandler(e func(error)) {
|
||||||
|
c.Lock()
|
||||||
|
c.handler = e
|
||||||
|
c.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Closer) wait() chan struct{} {
|
||||||
|
c.Lock()
|
||||||
|
if c.done == nil {
|
||||||
|
c.done = make(chan struct{})
|
||||||
|
}
|
||||||
|
c.Unlock()
|
||||||
|
|
||||||
|
return c.done
|
||||||
|
}
|
||||||
|
|
||||||
|
type wait struct {
|
||||||
|
timeout time.Duration
|
||||||
|
priority uint
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wait) done(start time.Time, priority uint, wg *sync.WaitGroup) {
|
||||||
|
done := make(chan struct{})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
wg.Wait()
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-w.after(start, priority):
|
||||||
|
case <-done:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wait) after(start time.Time, priority uint) <-chan time.Time {
|
||||||
|
timeout := (w.timeout - time.Since(start)) / time.Duration(w.priority) * time.Duration(priority)
|
||||||
|
w.priority -= priority
|
||||||
|
|
||||||
|
if timeout <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.After(timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
type prioritySlice []uint8
|
||||||
|
|
||||||
|
func (p prioritySlice) Len() int { return len(p) }
|
||||||
|
func (p prioritySlice) Less(i, j int) bool { return p[i] > p[j] }
|
||||||
|
func (p prioritySlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||||
112
priority/closer_example_test.go
Normal file
112
priority/closer_example_test.go
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
package priority_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/closer/priority"
|
||||||
|
)
|
||||||
|
|
||||||
|
type closer struct {
|
||||||
|
msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint: forbidigo
|
||||||
|
func (c *closer) Close() error {
|
||||||
|
fmt.Print(c.msg)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func OpenDB() io.Closer {
|
||||||
|
return &closer{msg: "close db. "}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConsumer() io.Closer {
|
||||||
|
return &closer{msg: "close consumer. "}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleCloser_Wait() {
|
||||||
|
time.AfterFunc(time.Second/2, func() {
|
||||||
|
_ = syscall.Kill(syscall.Getpid(), syscall.SIGTERM)
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
cl := priority.New(
|
||||||
|
priority.WithTimeout(time.Second*5),
|
||||||
|
priority.WithHandleError(func(e error) {
|
||||||
|
fmt.Print(e)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
db := OpenDB()
|
||||||
|
cl.AddLast(db.Close)
|
||||||
|
|
||||||
|
// your consumer events
|
||||||
|
consumer := NewConsumer()
|
||||||
|
cl.AddFirst(consumer.Close)
|
||||||
|
|
||||||
|
s := http.Server{}
|
||||||
|
// your http listeners
|
||||||
|
listener, _ := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := s.Serve(listener); err != nil {
|
||||||
|
_ = cl.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
cl.Add(func() error {
|
||||||
|
fmt.Print("stop server. ")
|
||||||
|
|
||||||
|
return s.Shutdown(ctx)
|
||||||
|
})
|
||||||
|
|
||||||
|
cl.Wait(ctx, syscall.SIGTERM, syscall.SIGINT)
|
||||||
|
// Output: close consumer. stop server. close db.
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleCloser_Close() {
|
||||||
|
cl := priority.New()
|
||||||
|
|
||||||
|
cl.Add(func() error {
|
||||||
|
fmt.Print("normal stop. ")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}, func() error {
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
fmt.Print("long normal stop. ")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
cl.AddFirst(func() error {
|
||||||
|
fmt.Print("first stop. ")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
cl.AddLast(func() error {
|
||||||
|
fmt.Print("last stop. ")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
cl.AddByPriority(priority.First+1, func() error {
|
||||||
|
fmt.Print("run before first. ")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
cl.AddByPriority(priority.Normal-1, func() error {
|
||||||
|
fmt.Print("run after normal. ")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
cl.Close()
|
||||||
|
// Output: run before first. first stop. normal stop. long normal stop. run after normal. last stop.
|
||||||
|
}
|
||||||
63
priority/closer_test.go
Normal file
63
priority/closer_test.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package priority_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/closer/priority"
|
||||||
|
"gitoa.ru/go-4devs/closer/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPriority_Close(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
cl := priority.Closer{}
|
||||||
|
closed := &test.Closed{}
|
||||||
|
|
||||||
|
go cl.Wait(context.Background())
|
||||||
|
|
||||||
|
cl.Add()
|
||||||
|
cl.Add(closed.CloseFnc("one", time.Microsecond))
|
||||||
|
cl.AddLast(closed.CloseFnc("last", time.Microsecond))
|
||||||
|
cl.AddFirst(closed.CloseFnc("first", time.Millisecond))
|
||||||
|
cl.AddByPriority(priority.Normal, closed.CloseFnc("one", time.Microsecond), closed.CloseFnc("one", time.Microsecond))
|
||||||
|
test.RequireNil(t, cl.Close())
|
||||||
|
closed.TestEqual(t, []string{"first", "one", "one", "one", "last"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPriority_Wait_Timeout(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
closed := &test.Closed{}
|
||||||
|
|
||||||
|
cl := priority.New(priority.WithTimeout(time.Second/5), priority.WithHandleError(func(error) {}))
|
||||||
|
cl.Add(closed.CloseFnc("one", 0))
|
||||||
|
cl.AddByPriority(priority.First, closed.CloseFnc("first", time.Second))
|
||||||
|
cl.AddByPriority(priority.Last, closed.CloseFnc("last", 0))
|
||||||
|
cl.AddLast(func() error {
|
||||||
|
return test.ErrClose
|
||||||
|
})
|
||||||
|
|
||||||
|
time.AfterFunc(time.Second/3, func() {
|
||||||
|
test.RequireNil(t, syscall.Kill(syscall.Getpid(), syscall.SIGTERM))
|
||||||
|
})
|
||||||
|
cl.Wait(context.Background(), syscall.SIGTERM)
|
||||||
|
closed.TestEqual(t, []string{"one", "last", "first"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPriority_Wait(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
cl := priority.New(priority.WithHandleError(func(err error) {
|
||||||
|
test.RequireError(t, err, test.ErrClose)
|
||||||
|
}))
|
||||||
|
cl.AddLast(func() error {
|
||||||
|
return test.ErrClose
|
||||||
|
})
|
||||||
|
cl.Wait(ctx)
|
||||||
|
}
|
||||||
19
routine/LICENSE
Normal file
19
routine/LICENSE
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
MIT License Copyright (c) 2020 4devs
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice (including the next
|
||||||
|
paragraph) shall be included in all copies or substantial portions of the
|
||||||
|
Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
||||||
|
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
||||||
|
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
3
routine/go.mod
Normal file
3
routine/go.mod
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module gitoa.ru/go-4devs/closer/routine
|
||||||
|
|
||||||
|
go 1.16
|
||||||
55
routine/runner.go
Normal file
55
routine/runner.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package routine
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
// nolint: gochecknoglobals
|
||||||
|
var global = &WaitGroup{}
|
||||||
|
|
||||||
|
// Go run routine and add wait group.
|
||||||
|
func Go(fnc func()) {
|
||||||
|
global.Go(fnc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run run routines and add wait group.
|
||||||
|
func Run(fnc ...func()) {
|
||||||
|
global.Run(fnc...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close global routines.
|
||||||
|
func Close() error {
|
||||||
|
return global.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait wait all go routines.
|
||||||
|
func Wait() {
|
||||||
|
global.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitGroup run func and wait when done.
|
||||||
|
type WaitGroup struct {
|
||||||
|
sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close wait all routines and implement Closer.
|
||||||
|
func (wg *WaitGroup) Close() error {
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go add wait group to routines.
|
||||||
|
func (wg *WaitGroup) Go(fnc func()) {
|
||||||
|
wg.Run(fnc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run functions in routine and add wait group.
|
||||||
|
func (wg *WaitGroup) Run(fnc ...func()) {
|
||||||
|
wg.Add(len(fnc))
|
||||||
|
|
||||||
|
for i := range fnc {
|
||||||
|
go func(i int) {
|
||||||
|
defer wg.Done()
|
||||||
|
fnc[i]()
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
31
routine/runner_examplte_test.go
Normal file
31
routine/runner_examplte_test.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package routine_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/closer/routine"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleGo() {
|
||||||
|
defer routine.Close()
|
||||||
|
routine.Go(func() {
|
||||||
|
time.Sleep(time.Microsecond)
|
||||||
|
fmt.Print("do some job")
|
||||||
|
})
|
||||||
|
|
||||||
|
// Output: do some job
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleRun() {
|
||||||
|
defer routine.Close()
|
||||||
|
|
||||||
|
routine.Run(func() {
|
||||||
|
time.Sleep(time.Microsecond)
|
||||||
|
fmt.Print("do some job. ")
|
||||||
|
}, func() {
|
||||||
|
fmt.Print("fast job in goroutine. ")
|
||||||
|
})
|
||||||
|
|
||||||
|
// Output: fast job in goroutine. do some job.
|
||||||
|
}
|
||||||
69
routine/runner_test.go
Normal file
69
routine/runner_test.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package routine_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/closer/routine"
|
||||||
|
)
|
||||||
|
|
||||||
|
func equal(t *testing.T, exp, res int) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
if exp != res {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGo(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
dt := make(map[string]int)
|
||||||
|
|
||||||
|
var mu sync.Mutex
|
||||||
|
|
||||||
|
fnc := func(name string) func() {
|
||||||
|
return func() {
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
mu.Lock()
|
||||||
|
dt[name]++
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
equal(t, 0, dt["once"])
|
||||||
|
|
||||||
|
routine.Go(fnc("once"))
|
||||||
|
routine.Run(fnc("twice"), fnc("twice"))
|
||||||
|
routine.Wait()
|
||||||
|
|
||||||
|
equal(t, 1, dt["once"])
|
||||||
|
equal(t, 2, dt["twice"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClose(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
dt := make(map[string]int)
|
||||||
|
|
||||||
|
var mu sync.Mutex
|
||||||
|
|
||||||
|
fnc := func(name string) func() {
|
||||||
|
return func() {
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
mu.Lock()
|
||||||
|
dt[name]++
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
routine.Go(fnc("once"))
|
||||||
|
routine.Run(fnc("twice"), fnc("twice"))
|
||||||
|
|
||||||
|
if err := routine.Close(); err != nil {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
equal(t, 1, dt["once"])
|
||||||
|
equal(t, 2, dt["twice"])
|
||||||
|
}
|
||||||
15
shutdown.go
Normal file
15
shutdown.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package closer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Shutdown(fnc func(context.Context) error, timeout time.Duration) func() error {
|
||||||
|
return func() error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
return fnc(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
60
test/closer.go
Normal file
60
test/closer.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrClose = errors.New("some error")
|
||||||
|
|
||||||
|
type Closed struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
d []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Closed) TestLen(t *testing.T, exp int) {
|
||||||
|
if len(c.d) != exp {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Closed) TestEqual(t *testing.T, exp []string) {
|
||||||
|
if len(c.d) != len(exp) {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range exp {
|
||||||
|
if exp[i] != c.d[i] {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Closed) CloseFnc(name string, sleep time.Duration) func() error {
|
||||||
|
return func() error {
|
||||||
|
time.Sleep(sleep)
|
||||||
|
c.mu.Lock()
|
||||||
|
c.d = append(c.d, name)
|
||||||
|
c.mu.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RequireNil(t *testing.T, exp interface{}) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
if exp != nil {
|
||||||
|
t.Fatalf("expected nil, got %v", exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RequireError(t *testing.T, exp, target error) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
if !errors.Is(exp, target) {
|
||||||
|
t.Fatalf("expected %s, got %s", exp, target)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user