mirror of
https://github.com/Luzifer/duplicity-backup.git
synced 2024-12-30 07:11:23 +00:00
536 lines
20 KiB
Go
536 lines
20 KiB
Go
/*
|
|
Ginkgo is a BDD-style testing framework for Golang
|
|
|
|
The godoc documentation describes Ginkgo's API. More comprehensive documentation (with examples!) is available at http://onsi.github.io/ginkgo/
|
|
|
|
Ginkgo's preferred matcher library is [Gomega](http://github.com/onsi/gomega)
|
|
|
|
Ginkgo on Github: http://github.com/onsi/ginkgo
|
|
|
|
Ginkgo is MIT-Licensed
|
|
*/
|
|
package ginkgo
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/onsi/ginkgo/config"
|
|
"github.com/onsi/ginkgo/internal/codelocation"
|
|
"github.com/onsi/ginkgo/internal/failer"
|
|
"github.com/onsi/ginkgo/internal/remote"
|
|
"github.com/onsi/ginkgo/internal/suite"
|
|
"github.com/onsi/ginkgo/internal/testingtproxy"
|
|
"github.com/onsi/ginkgo/internal/writer"
|
|
"github.com/onsi/ginkgo/reporters"
|
|
"github.com/onsi/ginkgo/reporters/stenographer"
|
|
"github.com/onsi/ginkgo/types"
|
|
)
|
|
|
|
const GINKGO_VERSION = config.VERSION
|
|
const GINKGO_PANIC = `
|
|
Your test failed.
|
|
Ginkgo panics to prevent subsequent assertions from running.
|
|
Normally Ginkgo rescues this panic so you shouldn't see it.
|
|
|
|
But, if you make an assertion in a goroutine, Ginkgo can't capture the panic.
|
|
To circumvent this, you should call
|
|
|
|
defer GinkgoRecover()
|
|
|
|
at the top of the goroutine that caused this panic.
|
|
`
|
|
const defaultTimeout = 1
|
|
|
|
var globalSuite *suite.Suite
|
|
var globalFailer *failer.Failer
|
|
|
|
func init() {
|
|
config.Flags(flag.CommandLine, "ginkgo", true)
|
|
GinkgoWriter = writer.New(os.Stdout)
|
|
globalFailer = failer.New()
|
|
globalSuite = suite.New(globalFailer)
|
|
}
|
|
|
|
//GinkgoWriter implements an io.Writer
|
|
//When running in verbose mode any writes to GinkgoWriter will be immediately printed
|
|
//to stdout. Otherwise, GinkgoWriter will buffer any writes produced during the current test and flush them to screen
|
|
//only if the current test fails.
|
|
var GinkgoWriter io.Writer
|
|
|
|
//The interface by which Ginkgo receives *testing.T
|
|
type GinkgoTestingT interface {
|
|
Fail()
|
|
}
|
|
|
|
//GinkgoParallelNode returns the parallel node number for the current ginkgo process
|
|
//The node number is 1-indexed
|
|
func GinkgoParallelNode() int {
|
|
return config.GinkgoConfig.ParallelNode
|
|
}
|
|
|
|
//Some matcher libraries or legacy codebases require a *testing.T
|
|
//GinkgoT implements an interface analogous to *testing.T and can be used if
|
|
//the library in question accepts *testing.T through an interface
|
|
//
|
|
// For example, with testify:
|
|
// assert.Equal(GinkgoT(), 123, 123, "they should be equal")
|
|
//
|
|
// Or with gomock:
|
|
// gomock.NewController(GinkgoT())
|
|
//
|
|
// GinkgoT() takes an optional offset argument that can be used to get the
|
|
// correct line number associated with the failure.
|
|
func GinkgoT(optionalOffset ...int) GinkgoTInterface {
|
|
offset := 3
|
|
if len(optionalOffset) > 0 {
|
|
offset = optionalOffset[0]
|
|
}
|
|
return testingtproxy.New(GinkgoWriter, Fail, offset)
|
|
}
|
|
|
|
//The interface returned by GinkgoT(). This covers most of the methods
|
|
//in the testing package's T.
|
|
type GinkgoTInterface interface {
|
|
Fail()
|
|
Error(args ...interface{})
|
|
Errorf(format string, args ...interface{})
|
|
FailNow()
|
|
Fatal(args ...interface{})
|
|
Fatalf(format string, args ...interface{})
|
|
Log(args ...interface{})
|
|
Logf(format string, args ...interface{})
|
|
Failed() bool
|
|
Parallel()
|
|
Skip(args ...interface{})
|
|
Skipf(format string, args ...interface{})
|
|
SkipNow()
|
|
Skipped() bool
|
|
}
|
|
|
|
//Custom Ginkgo test reporters must implement the Reporter interface.
|
|
//
|
|
//The custom reporter is passed in a SuiteSummary when the suite begins and ends,
|
|
//and a SpecSummary just before a spec begins and just after a spec ends
|
|
type Reporter reporters.Reporter
|
|
|
|
//Asynchronous specs are given a channel of the Done type. You must close or write to the channel
|
|
//to tell Ginkgo that your async test is done.
|
|
type Done chan<- interface{}
|
|
|
|
//GinkgoTestDescription represents the information about the current running test returned by CurrentGinkgoTestDescription
|
|
// FullTestText: a concatenation of ComponentTexts and the TestText
|
|
// ComponentTexts: a list of all texts for the Describes & Contexts leading up to the current test
|
|
// TestText: the text in the actual It or Measure node
|
|
// IsMeasurement: true if the current test is a measurement
|
|
// FileName: the name of the file containing the current test
|
|
// LineNumber: the line number for the current test
|
|
// Failed: if the current test has failed, this will be true (useful in an AfterEach)
|
|
type GinkgoTestDescription struct {
|
|
FullTestText string
|
|
ComponentTexts []string
|
|
TestText string
|
|
|
|
IsMeasurement bool
|
|
|
|
FileName string
|
|
LineNumber int
|
|
|
|
Failed bool
|
|
}
|
|
|
|
//CurrentGinkgoTestDescripton returns information about the current running test.
|
|
func CurrentGinkgoTestDescription() GinkgoTestDescription {
|
|
summary, ok := globalSuite.CurrentRunningSpecSummary()
|
|
if !ok {
|
|
return GinkgoTestDescription{}
|
|
}
|
|
|
|
subjectCodeLocation := summary.ComponentCodeLocations[len(summary.ComponentCodeLocations)-1]
|
|
|
|
return GinkgoTestDescription{
|
|
ComponentTexts: summary.ComponentTexts[1:],
|
|
FullTestText: strings.Join(summary.ComponentTexts[1:], " "),
|
|
TestText: summary.ComponentTexts[len(summary.ComponentTexts)-1],
|
|
IsMeasurement: summary.IsMeasurement,
|
|
FileName: subjectCodeLocation.FileName,
|
|
LineNumber: subjectCodeLocation.LineNumber,
|
|
Failed: summary.HasFailureState(),
|
|
}
|
|
}
|
|
|
|
//Measurement tests receive a Benchmarker.
|
|
//
|
|
//You use the Time() function to time how long the passed in body function takes to run
|
|
//You use the RecordValue() function to track arbitrary numerical measurements.
|
|
//The optional info argument is passed to the test reporter and can be used to
|
|
// provide the measurement data to a custom reporter with context.
|
|
//
|
|
//See http://onsi.github.io/ginkgo/#benchmark_tests for more details
|
|
type Benchmarker interface {
|
|
Time(name string, body func(), info ...interface{}) (elapsedTime time.Duration)
|
|
RecordValue(name string, value float64, info ...interface{})
|
|
}
|
|
|
|
//RunSpecs is the entry point for the Ginkgo test runner.
|
|
//You must call this within a Golang testing TestX(t *testing.T) function.
|
|
//
|
|
//To bootstrap a test suite you can use the Ginkgo CLI:
|
|
//
|
|
// ginkgo bootstrap
|
|
func RunSpecs(t GinkgoTestingT, description string) bool {
|
|
specReporters := []Reporter{buildDefaultReporter()}
|
|
return RunSpecsWithCustomReporters(t, description, specReporters)
|
|
}
|
|
|
|
//To run your tests with Ginkgo's default reporter and your custom reporter(s), replace
|
|
//RunSpecs() with this method.
|
|
func RunSpecsWithDefaultAndCustomReporters(t GinkgoTestingT, description string, specReporters []Reporter) bool {
|
|
specReporters = append([]Reporter{buildDefaultReporter()}, specReporters...)
|
|
return RunSpecsWithCustomReporters(t, description, specReporters)
|
|
}
|
|
|
|
//To run your tests with your custom reporter(s) (and *not* Ginkgo's default reporter), replace
|
|
//RunSpecs() with this method. Note that parallel tests will not work correctly without the default reporter
|
|
func RunSpecsWithCustomReporters(t GinkgoTestingT, description string, specReporters []Reporter) bool {
|
|
writer := GinkgoWriter.(*writer.Writer)
|
|
writer.SetStream(config.DefaultReporterConfig.Verbose)
|
|
reporters := make([]reporters.Reporter, len(specReporters))
|
|
for i, reporter := range specReporters {
|
|
reporters[i] = reporter
|
|
}
|
|
passed, hasFocusedTests := globalSuite.Run(t, description, reporters, writer, config.GinkgoConfig)
|
|
if passed && hasFocusedTests {
|
|
fmt.Println("PASS | FOCUSED")
|
|
os.Exit(types.GINKGO_FOCUS_EXIT_CODE)
|
|
}
|
|
return passed
|
|
}
|
|
|
|
func buildDefaultReporter() Reporter {
|
|
remoteReportingServer := config.GinkgoConfig.StreamHost
|
|
if remoteReportingServer == "" {
|
|
stenographer := stenographer.New(!config.DefaultReporterConfig.NoColor)
|
|
return reporters.NewDefaultReporter(config.DefaultReporterConfig, stenographer)
|
|
} else {
|
|
return remote.NewForwardingReporter(remoteReportingServer, &http.Client{}, remote.NewOutputInterceptor())
|
|
}
|
|
}
|
|
|
|
//Skip notifies Ginkgo that the current spec should be skipped.
|
|
func Skip(message string, callerSkip ...int) {
|
|
skip := 0
|
|
if len(callerSkip) > 0 {
|
|
skip = callerSkip[0]
|
|
}
|
|
|
|
globalFailer.Skip(message, codelocation.New(skip+1))
|
|
panic(GINKGO_PANIC)
|
|
}
|
|
|
|
//Fail notifies Ginkgo that the current spec has failed. (Gomega will call Fail for you automatically when an assertion fails.)
|
|
func Fail(message string, callerSkip ...int) {
|
|
skip := 0
|
|
if len(callerSkip) > 0 {
|
|
skip = callerSkip[0]
|
|
}
|
|
|
|
globalFailer.Fail(message, codelocation.New(skip+1))
|
|
panic(GINKGO_PANIC)
|
|
}
|
|
|
|
//GinkgoRecover should be deferred at the top of any spawned goroutine that (may) call `Fail`
|
|
//Since Gomega assertions call fail, you should throw a `defer GinkgoRecover()` at the top of any goroutine that
|
|
//calls out to Gomega
|
|
//
|
|
//Here's why: Ginkgo's `Fail` method records the failure and then panics to prevent
|
|
//further assertions from running. This panic must be recovered. Ginkgo does this for you
|
|
//if the panic originates in a Ginkgo node (an It, BeforeEach, etc...)
|
|
//
|
|
//Unfortunately, if a panic originates on a goroutine *launched* from one of these nodes there's no
|
|
//way for Ginkgo to rescue the panic. To do this, you must remember to `defer GinkgoRecover()` at the top of such a goroutine.
|
|
func GinkgoRecover() {
|
|
e := recover()
|
|
if e != nil {
|
|
globalFailer.Panic(codelocation.New(1), e)
|
|
}
|
|
}
|
|
|
|
//Describe blocks allow you to organize your specs. A Describe block can contain any number of
|
|
//BeforeEach, AfterEach, JustBeforeEach, It, and Measurement blocks.
|
|
//
|
|
//In addition you can nest Describe and Context blocks. Describe and Context blocks are functionally
|
|
//equivalent. The difference is purely semantic -- you typical Describe the behavior of an object
|
|
//or method and, within that Describe, outline a number of Contexts.
|
|
func Describe(text string, body func()) bool {
|
|
globalSuite.PushContainerNode(text, body, types.FlagTypeNone, codelocation.New(1))
|
|
return true
|
|
}
|
|
|
|
//You can focus the tests within a describe block using FDescribe
|
|
func FDescribe(text string, body func()) bool {
|
|
globalSuite.PushContainerNode(text, body, types.FlagTypeFocused, codelocation.New(1))
|
|
return true
|
|
}
|
|
|
|
//You can mark the tests within a describe block as pending using PDescribe
|
|
func PDescribe(text string, body func()) bool {
|
|
globalSuite.PushContainerNode(text, body, types.FlagTypePending, codelocation.New(1))
|
|
return true
|
|
}
|
|
|
|
//You can mark the tests within a describe block as pending using XDescribe
|
|
func XDescribe(text string, body func()) bool {
|
|
globalSuite.PushContainerNode(text, body, types.FlagTypePending, codelocation.New(1))
|
|
return true
|
|
}
|
|
|
|
//Context blocks allow you to organize your specs. A Context block can contain any number of
|
|
//BeforeEach, AfterEach, JustBeforeEach, It, and Measurement blocks.
|
|
//
|
|
//In addition you can nest Describe and Context blocks. Describe and Context blocks are functionally
|
|
//equivalent. The difference is purely semantic -- you typical Describe the behavior of an object
|
|
//or method and, within that Describe, outline a number of Contexts.
|
|
func Context(text string, body func()) bool {
|
|
globalSuite.PushContainerNode(text, body, types.FlagTypeNone, codelocation.New(1))
|
|
return true
|
|
}
|
|
|
|
//You can focus the tests within a describe block using FContext
|
|
func FContext(text string, body func()) bool {
|
|
globalSuite.PushContainerNode(text, body, types.FlagTypeFocused, codelocation.New(1))
|
|
return true
|
|
}
|
|
|
|
//You can mark the tests within a describe block as pending using PContext
|
|
func PContext(text string, body func()) bool {
|
|
globalSuite.PushContainerNode(text, body, types.FlagTypePending, codelocation.New(1))
|
|
return true
|
|
}
|
|
|
|
//You can mark the tests within a describe block as pending using XContext
|
|
func XContext(text string, body func()) bool {
|
|
globalSuite.PushContainerNode(text, body, types.FlagTypePending, codelocation.New(1))
|
|
return true
|
|
}
|
|
|
|
//It blocks contain your test code and assertions. You cannot nest any other Ginkgo blocks
|
|
//within an It block.
|
|
//
|
|
//Ginkgo will normally run It blocks synchronously. To perform asynchronous tests, pass a
|
|
//function that accepts a Done channel. When you do this, you can also provide an optional timeout.
|
|
func It(text string, body interface{}, timeout ...float64) bool {
|
|
globalSuite.PushItNode(text, body, types.FlagTypeNone, codelocation.New(1), parseTimeout(timeout...))
|
|
return true
|
|
}
|
|
|
|
//You can focus individual Its using FIt
|
|
func FIt(text string, body interface{}, timeout ...float64) bool {
|
|
globalSuite.PushItNode(text, body, types.FlagTypeFocused, codelocation.New(1), parseTimeout(timeout...))
|
|
return true
|
|
}
|
|
|
|
//You can mark Its as pending using PIt
|
|
func PIt(text string, _ ...interface{}) bool {
|
|
globalSuite.PushItNode(text, func() {}, types.FlagTypePending, codelocation.New(1), 0)
|
|
return true
|
|
}
|
|
|
|
//You can mark Its as pending using XIt
|
|
func XIt(text string, _ ...interface{}) bool {
|
|
globalSuite.PushItNode(text, func() {}, types.FlagTypePending, codelocation.New(1), 0)
|
|
return true
|
|
}
|
|
|
|
//By allows you to better document large Its.
|
|
//
|
|
//Generally you should try to keep your Its short and to the point. This is not always possible, however,
|
|
//especially in the context of integration tests that capture a particular workflow.
|
|
//
|
|
//By allows you to document such flows. By must be called within a runnable node (It, BeforeEach, Measure, etc...)
|
|
//By will simply log the passed in text to the GinkgoWriter. If By is handed a function it will immediately run the function.
|
|
func By(text string, callbacks ...func()) {
|
|
preamble := "\x1b[1mSTEP\x1b[0m"
|
|
if config.DefaultReporterConfig.NoColor {
|
|
preamble = "STEP"
|
|
}
|
|
fmt.Fprintln(GinkgoWriter, preamble+": "+text)
|
|
if len(callbacks) == 1 {
|
|
callbacks[0]()
|
|
}
|
|
if len(callbacks) > 1 {
|
|
panic("just one callback per By, please")
|
|
}
|
|
}
|
|
|
|
//Measure blocks run the passed in body function repeatedly (determined by the samples argument)
|
|
//and accumulate metrics provided to the Benchmarker by the body function.
|
|
//
|
|
//The body function must have the signature:
|
|
// func(b Benchmarker)
|
|
func Measure(text string, body interface{}, samples int) bool {
|
|
globalSuite.PushMeasureNode(text, body, types.FlagTypeNone, codelocation.New(1), samples)
|
|
return true
|
|
}
|
|
|
|
//You can focus individual Measures using FMeasure
|
|
func FMeasure(text string, body interface{}, samples int) bool {
|
|
globalSuite.PushMeasureNode(text, body, types.FlagTypeFocused, codelocation.New(1), samples)
|
|
return true
|
|
}
|
|
|
|
//You can mark Maeasurements as pending using PMeasure
|
|
func PMeasure(text string, _ ...interface{}) bool {
|
|
globalSuite.PushMeasureNode(text, func(b Benchmarker) {}, types.FlagTypePending, codelocation.New(1), 0)
|
|
return true
|
|
}
|
|
|
|
//You can mark Maeasurements as pending using XMeasure
|
|
func XMeasure(text string, _ ...interface{}) bool {
|
|
globalSuite.PushMeasureNode(text, func(b Benchmarker) {}, types.FlagTypePending, codelocation.New(1), 0)
|
|
return true
|
|
}
|
|
|
|
//BeforeSuite blocks are run just once before any specs are run. When running in parallel, each
|
|
//parallel node process will call BeforeSuite.
|
|
//
|
|
//BeforeSuite blocks can be made asynchronous by providing a body function that accepts a Done channel
|
|
//
|
|
//You may only register *one* BeforeSuite handler per test suite. You typically do so in your bootstrap file at the top level.
|
|
func BeforeSuite(body interface{}, timeout ...float64) bool {
|
|
globalSuite.SetBeforeSuiteNode(body, codelocation.New(1), parseTimeout(timeout...))
|
|
return true
|
|
}
|
|
|
|
//AfterSuite blocks are *always* run after all the specs regardless of whether specs have passed or failed.
|
|
//Moreover, if Ginkgo receives an interrupt signal (^C) it will attempt to run the AfterSuite before exiting.
|
|
//
|
|
//When running in parallel, each parallel node process will call AfterSuite.
|
|
//
|
|
//AfterSuite blocks can be made asynchronous by providing a body function that accepts a Done channel
|
|
//
|
|
//You may only register *one* AfterSuite handler per test suite. You typically do so in your bootstrap file at the top level.
|
|
func AfterSuite(body interface{}, timeout ...float64) bool {
|
|
globalSuite.SetAfterSuiteNode(body, codelocation.New(1), parseTimeout(timeout...))
|
|
return true
|
|
}
|
|
|
|
//SynchronizedBeforeSuite blocks are primarily meant to solve the problem of setting up singleton external resources shared across
|
|
//nodes when running tests in parallel. For example, say you have a shared database that you can only start one instance of that
|
|
//must be used in your tests. When running in parallel, only one node should set up the database and all other nodes should wait
|
|
//until that node is done before running.
|
|
//
|
|
//SynchronizedBeforeSuite accomplishes this by taking *two* function arguments. The first is only run on parallel node #1. The second is
|
|
//run on all nodes, but *only* after the first function completes succesfully. Ginkgo also makes it possible to send data from the first function (on Node 1)
|
|
//to the second function (on all the other nodes).
|
|
//
|
|
//The functions have the following signatures. The first function (which only runs on node 1) has the signature:
|
|
//
|
|
// func() []byte
|
|
//
|
|
//or, to run asynchronously:
|
|
//
|
|
// func(done Done) []byte
|
|
//
|
|
//The byte array returned by the first function is then passed to the second function, which has the signature:
|
|
//
|
|
// func(data []byte)
|
|
//
|
|
//or, to run asynchronously:
|
|
//
|
|
// func(data []byte, done Done)
|
|
//
|
|
//Here's a simple pseudo-code example that starts a shared database on Node 1 and shares the database's address with the other nodes:
|
|
//
|
|
// var dbClient db.Client
|
|
// var dbRunner db.Runner
|
|
//
|
|
// var _ = SynchronizedBeforeSuite(func() []byte {
|
|
// dbRunner = db.NewRunner()
|
|
// err := dbRunner.Start()
|
|
// Ω(err).ShouldNot(HaveOccurred())
|
|
// return []byte(dbRunner.URL)
|
|
// }, func(data []byte) {
|
|
// dbClient = db.NewClient()
|
|
// err := dbClient.Connect(string(data))
|
|
// Ω(err).ShouldNot(HaveOccurred())
|
|
// })
|
|
func SynchronizedBeforeSuite(node1Body interface{}, allNodesBody interface{}, timeout ...float64) bool {
|
|
globalSuite.SetSynchronizedBeforeSuiteNode(
|
|
node1Body,
|
|
allNodesBody,
|
|
codelocation.New(1),
|
|
parseTimeout(timeout...),
|
|
)
|
|
return true
|
|
}
|
|
|
|
//SynchronizedAfterSuite blocks complement the SynchronizedBeforeSuite blocks in solving the problem of setting up
|
|
//external singleton resources shared across nodes when running tests in parallel.
|
|
//
|
|
//SynchronizedAfterSuite accomplishes this by taking *two* function arguments. The first runs on all nodes. The second runs only on parallel node #1
|
|
//and *only* after all other nodes have finished and exited. This ensures that node 1, and any resources it is running, remain alive until
|
|
//all other nodes are finished.
|
|
//
|
|
//Both functions have the same signature: either func() or func(done Done) to run asynchronously.
|
|
//
|
|
//Here's a pseudo-code example that complements that given in SynchronizedBeforeSuite. Here, SynchronizedAfterSuite is used to tear down the shared database
|
|
//only after all nodes have finished:
|
|
//
|
|
// var _ = SynchronizedAfterSuite(func() {
|
|
// dbClient.Cleanup()
|
|
// }, func() {
|
|
// dbRunner.Stop()
|
|
// })
|
|
func SynchronizedAfterSuite(allNodesBody interface{}, node1Body interface{}, timeout ...float64) bool {
|
|
globalSuite.SetSynchronizedAfterSuiteNode(
|
|
allNodesBody,
|
|
node1Body,
|
|
codelocation.New(1),
|
|
parseTimeout(timeout...),
|
|
)
|
|
return true
|
|
}
|
|
|
|
//BeforeEach blocks are run before It blocks. When multiple BeforeEach blocks are defined in nested
|
|
//Describe and Context blocks the outermost BeforeEach blocks are run first.
|
|
//
|
|
//Like It blocks, BeforeEach blocks can be made asynchronous by providing a body function that accepts
|
|
//a Done channel
|
|
func BeforeEach(body interface{}, timeout ...float64) bool {
|
|
globalSuite.PushBeforeEachNode(body, codelocation.New(1), parseTimeout(timeout...))
|
|
return true
|
|
}
|
|
|
|
//JustBeforeEach blocks are run before It blocks but *after* all BeforeEach blocks. For more details,
|
|
//read the [documentation](http://onsi.github.io/ginkgo/#separating_creation_and_configuration_)
|
|
//
|
|
//Like It blocks, BeforeEach blocks can be made asynchronous by providing a body function that accepts
|
|
//a Done channel
|
|
func JustBeforeEach(body interface{}, timeout ...float64) bool {
|
|
globalSuite.PushJustBeforeEachNode(body, codelocation.New(1), parseTimeout(timeout...))
|
|
return true
|
|
}
|
|
|
|
//AfterEach blocks are run after It blocks. When multiple AfterEach blocks are defined in nested
|
|
//Describe and Context blocks the innermost AfterEach blocks are run first.
|
|
//
|
|
//Like It blocks, AfterEach blocks can be made asynchronous by providing a body function that accepts
|
|
//a Done channel
|
|
func AfterEach(body interface{}, timeout ...float64) bool {
|
|
globalSuite.PushAfterEachNode(body, codelocation.New(1), parseTimeout(timeout...))
|
|
return true
|
|
}
|
|
|
|
func parseTimeout(timeout ...float64) time.Duration {
|
|
if len(timeout) == 0 {
|
|
return time.Duration(defaultTimeout * int64(time.Second))
|
|
} else {
|
|
return time.Duration(timeout[0] * float64(time.Second))
|
|
}
|
|
}
|