From 34a636bf513e32ad6f6c2b1c72eecdb47c62d177 Mon Sep 17 00:00:00 2001 From: Arko Dasgupta Date: Tue, 1 Oct 2019 16:54:01 -0700 Subject: [PATCH] Fix flaky NetworkDB tests Fixed these tests : 1.TestNetworkDBIslands Addresses : https://github.com/docker/libnetwork/issues/2402 2.TestNetworkDBCRUDMediumCluster Addresses : https://github.com/docker/libnetwork/issues/2401 By : 1. Importing gotest.tools/poll to use poll.WaitOn Above function can be used to check a condition at regular intervals until a timeout is reached 2. Replacing Sleep with poll.WaitOn 2. Adding closeNetworkDBInstances to close remaining DBs Signed-off-by: Arko Dasgupta --- libnetwork/networkdb/networkdb_test.go | 67 +++--- libnetwork/vendor.conf | 2 +- libnetwork/vendor/gotest.tools/LICENSE | 209 +----------------- libnetwork/vendor/gotest.tools/README.md | 6 +- .../vendor/gotest.tools/assert/cmp/compare.go | 48 +++- .../vendor/gotest.tools/assert/cmp/result.go | 20 +- .../vendor/gotest.tools/assert/result.go | 1 - libnetwork/vendor/gotest.tools/go.mod | 8 + .../gotest.tools/internal/difflib/difflib.go | 25 ++- .../gotest.tools/internal/source/defers.go | 53 +++++ .../gotest.tools/internal/source/source.go | 203 ++++++++--------- libnetwork/vendor/gotest.tools/poll/check.go | 39 ++++ libnetwork/vendor/gotest.tools/poll/poll.go | 140 ++++++++++++ 13 files changed, 474 insertions(+), 347 deletions(-) create mode 100644 libnetwork/vendor/gotest.tools/go.mod create mode 100644 libnetwork/vendor/gotest.tools/internal/source/defers.go create mode 100644 libnetwork/vendor/gotest.tools/poll/check.go create mode 100644 libnetwork/vendor/gotest.tools/poll/poll.go diff --git a/libnetwork/networkdb/networkdb_test.go b/libnetwork/networkdb/networkdb_test.go index 5c7f2c92ff..e80c45992f 100644 --- a/libnetwork/networkdb/networkdb_test.go +++ b/libnetwork/networkdb/networkdb_test.go @@ -17,6 +17,7 @@ import ( "github.com/sirupsen/logrus" "gotest.tools/assert" is "gotest.tools/assert/cmp" + "gotest.tools/poll" // this takes care of the incontainer flag _ "github.com/docker/libnetwork/testutils" @@ -51,13 +52,17 @@ func createNetworkDBInstances(t *testing.T, num int, namePrefix string, conf *Co dbs = append(dbs, db) } - // Check that the cluster is properly created - for i := 0; i < num; i++ { - if num != len(dbs[i].ClusterPeers()) { - t.Fatalf("Number of nodes for %s into the cluster does not match %d != %d", - dbs[i].config.Hostname, num, len(dbs[i].ClusterPeers())) + // Wait till the cluster creation is successful + check := func(t poll.LogT) poll.Result { + // Check that the cluster is properly created + for i := 0; i < num; i++ { + if num != len(dbs[i].ClusterPeers()) { + return poll.Continue("%s:Waiting for cluser peers to be established", dbs[i].config.Hostname) + } } + return poll.Success() } + poll.WaitOn(t, check, poll.WithDelay(2*time.Second), poll.WithTimeout(20*time.Second)) return dbs } @@ -826,39 +831,51 @@ func TestNetworkDBIslands(t *testing.T) { for i := 0; i < 3; i++ { logrus.Infof("node %d leaving", i) dbs[i].Close() - time.Sleep(2 * time.Second) } // Give some time to let the system propagate the messages and free up the ports - time.Sleep(10 * time.Second) - - // Verify that the nodes are actually all gone and marked appropiately - for i := 3; i < 5; i++ { - assert.Check(t, is.Len(dbs[i].leftNodes, 3)) - assert.Check(t, is.Len(dbs[i].failedNodes, 0)) + check := func(t poll.LogT) poll.Result { + // Verify that the nodes are actually all gone and marked appropiately + for i := 3; i < 5; i++ { + if (len(dbs[i].leftNodes) != 3) || (len(dbs[i].failedNodes) != 0) { + return poll.Continue("%s:Waiting for all nodes to cleanly leave", dbs[i].config.Hostname) + } + } + return poll.Success() } + poll.WaitOn(t, check, poll.WithDelay(20*time.Second), poll.WithTimeout(120*time.Second)) // Spawn again the first 3 nodes with different names but same IP:port for i := 0; i < 3; i++ { logrus.Infof("node %d coming back", i) dbs[i].config.NodeID = stringid.TruncateID(stringid.GenerateRandomID()) dbs[i] = launchNode(t, *dbs[i].config) - time.Sleep(2 * time.Second) } // Give some time for the reconnect routine to run, it runs every 60s - time.Sleep(50 * time.Second) - - // Verify that the cluster is again all connected. Note that the 3 previous node did not do any join - for i := 0; i < 5; i++ { - assert.Check(t, is.Len(dbs[i].nodes, 5)) - assert.Check(t, is.Len(dbs[i].failedNodes, 0)) - if i < 3 { - // nodes from 0 to 3 has no left nodes - assert.Check(t, is.Len(dbs[i].leftNodes, 0)) - } else { - // nodes from 4 to 5 has the 3 previous left nodes - assert.Check(t, is.Len(dbs[i].leftNodes, 3)) + check = func(t poll.LogT) poll.Result { + // Verify that the cluster is again all connected. Note that the 3 previous node did not do any join + for i := 0; i < 5; i++ { + if len(dbs[i].nodes) != 5 { + return poll.Continue("%s:Waiting to connect to all nodes", dbs[i].config.Hostname) + } + if len(dbs[i].failedNodes) != 0 { + return poll.Continue("%s:Waiting for 0 failedNodes", dbs[i].config.Hostname) + } + if i < 3 { + // nodes from 0 to 3 has no left nodes + if len(dbs[i].leftNodes) != 0 { + return poll.Continue("%s:Waiting to have no leftNodes", dbs[i].config.Hostname) + } + } else { + // nodes from 4 to 5 has the 3 previous left nodes + if len(dbs[i].leftNodes) != 3 { + return poll.Continue("%s:Waiting to have 3 leftNodes", dbs[i].config.Hostname) + } + } } + return poll.Success() } + poll.WaitOn(t, check, poll.WithDelay(20*time.Second), poll.WithTimeout(120*time.Second)) + closeNetworkDBInstances(dbs) } diff --git a/libnetwork/vendor.conf b/libnetwork/vendor.conf index 78b8379fd4..ab8b2bcec9 100644 --- a/libnetwork/vendor.conf +++ b/libnetwork/vendor.conf @@ -51,5 +51,5 @@ golang.org/x/sync 1d60e4601c6fd243af51cc01ddf169918a5407ca github.com/pkg/errors ba968bfe8b2f7e042a574c888954fccecfa385b4 # v0.8.1 github.com/ishidawataru/sctp 6e2cb1366111dcf547c13531e3a263a067715847 -gotest.tools b6e20af1ed078cd01a6413b734051a292450b4cb # v2.1.0 +gotest.tools 1083505acf35a0bd8a696b26837e1fb3187a7a83 # v2.3.0 github.com/google/go-cmp 3af367b6b30c263d47e8895973edcca9a49cf029 # v0.2.0 diff --git a/libnetwork/vendor/gotest.tools/LICENSE b/libnetwork/vendor/gotest.tools/LICENSE index d645695673..aeaa2fac3d 100644 --- a/libnetwork/vendor/gotest.tools/LICENSE +++ b/libnetwork/vendor/gotest.tools/LICENSE @@ -1,202 +1,13 @@ +Copyright 2018 gotest.tools authors - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + http://www.apache.org/licenses/LICENSE-2.0 - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/libnetwork/vendor/gotest.tools/README.md b/libnetwork/vendor/gotest.tools/README.md index f5df204315..3155723dd7 100644 --- a/libnetwork/vendor/gotest.tools/README.md +++ b/libnetwork/vendor/gotest.tools/README.md @@ -3,7 +3,7 @@ A collection of packages to augment `testing` and support common patterns. [![GoDoc](https://godoc.org/gotest.tools?status.svg)](https://godoc.org/gotest.tools) -[![CircleCI](https://circleci.com/gh/gotestyourself/gotestyourself/tree/master.svg?style=shield)](https://circleci.com/gh/gotestyourself/gotestyourself/tree/master) +[![CircleCI](https://circleci.com/gh/gotestyourself/gotest.tools/tree/master.svg?style=shield)](https://circleci.com/gh/gotestyourself/gotest.tools/tree/master) [![Go Reportcard](https://goreportcard.com/badge/gotest.tools)](https://goreportcard.com/report/gotest.tools) @@ -29,3 +29,7 @@ A collection of packages to augment `testing` and support common patterns. * [gotest.tools/gotestsum](https://github.com/gotestyourself/gotestsum) - go test runner with custom output * [maxbrunsfeld/counterfeiter](https://github.com/maxbrunsfeld/counterfeiter) - generate fakes for interfaces * [jonboulle/clockwork](https://github.com/jonboulle/clockwork) - a fake clock for testing code that uses `time` + +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md). diff --git a/libnetwork/vendor/gotest.tools/assert/cmp/compare.go b/libnetwork/vendor/gotest.tools/assert/cmp/compare.go index ae03749e20..cf48d887ac 100644 --- a/libnetwork/vendor/gotest.tools/assert/cmp/compare.go +++ b/libnetwork/vendor/gotest.tools/assert/cmp/compare.go @@ -4,6 +4,7 @@ package cmp // import "gotest.tools/assert/cmp" import ( "fmt" "reflect" + "regexp" "strings" "github.com/google/go-cmp/cmp" @@ -58,6 +59,39 @@ func toResult(success bool, msg string) Result { return ResultFailure(msg) } +// RegexOrPattern may be either a *regexp.Regexp or a string that is a valid +// regexp pattern. +type RegexOrPattern interface{} + +// Regexp succeeds if value v matches regular expression re. +// +// Example: +// assert.Assert(t, cmp.Regexp("^[0-9a-f]{32}$", str)) +// r := regexp.MustCompile("^[0-9a-f]{32}$") +// assert.Assert(t, cmp.Regexp(r, str)) +func Regexp(re RegexOrPattern, v string) Comparison { + match := func(re *regexp.Regexp) Result { + return toResult( + re.MatchString(v), + fmt.Sprintf("value %q does not match regexp %q", v, re.String())) + } + + return func() Result { + switch regex := re.(type) { + case *regexp.Regexp: + return match(regex) + case string: + re, err := regexp.Compile(regex) + if err != nil { + return ResultFailure(err.Error()) + } + return match(re) + default: + return ResultFailure(fmt.Sprintf("invalid type %T for regex pattern", regex)) + } + } +} + // Equal succeeds if x == y. See assert.Equal for full documentation. func Equal(x, y interface{}) Comparison { return func() Result { @@ -186,7 +220,7 @@ func Error(err error, message string) Comparison { return ResultFailure("expected an error, got nil") case err.Error() != message: return ResultFailure(fmt.Sprintf( - "expected error %q, got %+v", message, err)) + "expected error %q, got %s", message, formatErrorMessage(err))) } return ResultSuccess } @@ -201,12 +235,22 @@ func ErrorContains(err error, substring string) Comparison { return ResultFailure("expected an error, got nil") case !strings.Contains(err.Error(), substring): return ResultFailure(fmt.Sprintf( - "expected error to contain %q, got %+v", substring, err)) + "expected error to contain %q, got %s", substring, formatErrorMessage(err))) } return ResultSuccess } } +func formatErrorMessage(err error) string { + if _, ok := err.(interface { + Cause() error + }); ok { + return fmt.Sprintf("%q\n%+v", err, err) + } + // This error was not wrapped with github.com/pkg/errors + return fmt.Sprintf("%q", err) +} + // Nil succeeds if obj is a nil interface, pointer, or function. // // Use NilError() for comparing errors. Use Len(obj, 0) for comparing slices, diff --git a/libnetwork/vendor/gotest.tools/assert/cmp/result.go b/libnetwork/vendor/gotest.tools/assert/cmp/result.go index 7c3c37dd72..204edda4bc 100644 --- a/libnetwork/vendor/gotest.tools/assert/cmp/result.go +++ b/libnetwork/vendor/gotest.tools/assert/cmp/result.go @@ -9,31 +9,37 @@ import ( "gotest.tools/internal/source" ) -// Result of a Comparison. +// A Result of a Comparison. type Result interface { Success() bool } -type result struct { +// StringResult is an implementation of Result that reports the error message +// string verbatim and does not provide any templating or formatting of the +// message. +type StringResult struct { success bool message string } -func (r result) Success() bool { +// Success returns true if the comparison was successful. +func (r StringResult) Success() bool { return r.success } -func (r result) FailureMessage() string { +// FailureMessage returns the message used to provide additional information +// about the failure. +func (r StringResult) FailureMessage() string { return r.message } // ResultSuccess is a constant which is returned by a ComparisonWithResult to // indicate success. -var ResultSuccess = result{success: true} +var ResultSuccess = StringResult{success: true} // ResultFailure returns a failed Result with a failure message. -func ResultFailure(message string) Result { - return result{message: message} +func ResultFailure(message string) StringResult { + return StringResult{message: message} } // ResultFromError returns ResultSuccess if err is nil. Otherwise ResultFailure diff --git a/libnetwork/vendor/gotest.tools/assert/result.go b/libnetwork/vendor/gotest.tools/assert/result.go index 3900264d0b..949d939619 100644 --- a/libnetwork/vendor/gotest.tools/assert/result.go +++ b/libnetwork/vendor/gotest.tools/assert/result.go @@ -70,7 +70,6 @@ func filterPrintableExpr(args []ast.Expr) []ast.Expr { result[i] = starExpr.X continue } - result[i] = nil } return result } diff --git a/libnetwork/vendor/gotest.tools/go.mod b/libnetwork/vendor/gotest.tools/go.mod new file mode 100644 index 0000000000..39d0a1a779 --- /dev/null +++ b/libnetwork/vendor/gotest.tools/go.mod @@ -0,0 +1,8 @@ +module gotest.tools + +require ( + github.com/google/go-cmp v0.2.0 + github.com/pkg/errors v0.8.0 + github.com/spf13/pflag v1.0.3 + golang.org/x/tools v0.0.0-20180810170437-e96c4e24768d +) diff --git a/libnetwork/vendor/gotest.tools/internal/difflib/difflib.go b/libnetwork/vendor/gotest.tools/internal/difflib/difflib.go index 5efa99c1d4..b6f486b9c9 100644 --- a/libnetwork/vendor/gotest.tools/internal/difflib/difflib.go +++ b/libnetwork/vendor/gotest.tools/internal/difflib/difflib.go @@ -1,4 +1,4 @@ -/* Package difflib is a partial port of Python difflib module. +/*Package difflib is a partial port of Python difflib module. Original source: https://github.com/pmezard/go-difflib @@ -20,12 +20,14 @@ func max(a, b int) int { return b } +// Match stores line numbers of size of match type Match struct { A int B int Size int } +// OpCode identifies the type of diff type OpCode struct { Tag byte I1 int @@ -73,19 +75,20 @@ type SequenceMatcher struct { opCodes []OpCode } +// NewMatcher returns a new SequenceMatcher func NewMatcher(a, b []string) *SequenceMatcher { m := SequenceMatcher{autoJunk: true} m.SetSeqs(a, b) return &m } -// Set two sequences to be compared. +// SetSeqs sets two sequences to be compared. func (m *SequenceMatcher) SetSeqs(a, b []string) { m.SetSeq1(a) m.SetSeq2(b) } -// Set the first sequence to be compared. The second sequence to be compared is +// SetSeq1 sets the first sequence to be compared. The second sequence to be compared is // not changed. // // SequenceMatcher computes and caches detailed information about the second @@ -103,7 +106,7 @@ func (m *SequenceMatcher) SetSeq1(a []string) { m.opCodes = nil } -// Set the second sequence to be compared. The first sequence to be compared is +// SetSeq2 sets the second sequence to be compared. The first sequence to be compared is // not changed. func (m *SequenceMatcher) SetSeq2(b []string) { if &b == &m.b { @@ -129,12 +132,12 @@ func (m *SequenceMatcher) chainB() { m.bJunk = map[string]struct{}{} if m.IsJunk != nil { junk := m.bJunk - for s, _ := range b2j { + for s := range b2j { if m.IsJunk(s) { junk[s] = struct{}{} } } - for s, _ := range junk { + for s := range junk { delete(b2j, s) } } @@ -149,7 +152,7 @@ func (m *SequenceMatcher) chainB() { popular[s] = struct{}{} } } - for s, _ := range popular { + for s := range popular { delete(b2j, s) } } @@ -259,7 +262,7 @@ func (m *SequenceMatcher) findLongestMatch(alo, ahi, blo, bhi int) Match { return Match{A: besti, B: bestj, Size: bestsize} } -// Return list of triples describing matching subsequences. +// GetMatchingBlocks returns a list of triples describing matching subsequences. // // Each triple is of the form (i, j, n), and means that // a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in @@ -323,7 +326,7 @@ func (m *SequenceMatcher) GetMatchingBlocks() []Match { return m.matchingBlocks } -// Return list of 5-tuples describing how to turn a into b. +// GetOpCodes returns a list of 5-tuples describing how to turn a into b. // // Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple // has i1 == j1 == 0, and remaining tuples have i1 == the i2 from the @@ -374,7 +377,7 @@ func (m *SequenceMatcher) GetOpCodes() []OpCode { return m.opCodes } -// Isolate change clusters by eliminating ranges with no changes. +// GetGroupedOpCodes isolates change clusters by eliminating ranges with no changes. // // Return a generator of groups with up to n lines of context. // Each group is in the same format as returned by GetOpCodes(). @@ -384,7 +387,7 @@ func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode { } codes := m.GetOpCodes() if len(codes) == 0 { - codes = []OpCode{OpCode{'e', 0, 1, 0, 1}} + codes = []OpCode{{'e', 0, 1, 0, 1}} } // Fixup leading and trailing groups if they show no changes. if codes[0].Tag == 'e' { diff --git a/libnetwork/vendor/gotest.tools/internal/source/defers.go b/libnetwork/vendor/gotest.tools/internal/source/defers.go new file mode 100644 index 0000000000..66cfafbb64 --- /dev/null +++ b/libnetwork/vendor/gotest.tools/internal/source/defers.go @@ -0,0 +1,53 @@ +package source + +import ( + "go/ast" + "go/token" + + "github.com/pkg/errors" +) + +func scanToDeferLine(fileset *token.FileSet, node ast.Node, lineNum int) ast.Node { + var matchedNode ast.Node + ast.Inspect(node, func(node ast.Node) bool { + switch { + case node == nil || matchedNode != nil: + return false + case fileset.Position(node.End()).Line == lineNum: + if funcLit, ok := node.(*ast.FuncLit); ok { + matchedNode = funcLit + return false + } + } + return true + }) + debug("defer line node: %s", debugFormatNode{matchedNode}) + return matchedNode +} + +func guessDefer(node ast.Node) (ast.Node, error) { + defers := collectDefers(node) + switch len(defers) { + case 0: + return nil, errors.New("failed to expression in defer") + case 1: + return defers[0].Call, nil + default: + return nil, errors.Errorf( + "ambiguous call expression: multiple (%d) defers in call block", + len(defers)) + } +} + +func collectDefers(node ast.Node) []*ast.DeferStmt { + var defers []*ast.DeferStmt + ast.Inspect(node, func(node ast.Node) bool { + if d, ok := node.(*ast.DeferStmt); ok { + defers = append(defers, d) + debug("defer: %s", debugFormatNode{d}) + return false + } + return true + }) + return defers +} diff --git a/libnetwork/vendor/gotest.tools/internal/source/source.go b/libnetwork/vendor/gotest.tools/internal/source/source.go index a05933cc33..8a5d0e8d35 100644 --- a/libnetwork/vendor/gotest.tools/internal/source/source.go +++ b/libnetwork/vendor/gotest.tools/internal/source/source.go @@ -24,106 +24,12 @@ func FormattedCallExprArg(stackIndex int, argPos int) (string, error) { if err != nil { return "", err } + if argPos >= len(args) { + return "", errors.New("failed to find expression") + } return FormatNode(args[argPos]) } -func getNodeAtLine(filename string, lineNum int) (ast.Node, error) { - fileset := token.NewFileSet() - astFile, err := parser.ParseFile(fileset, filename, nil, parser.AllErrors) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse source file: %s", filename) - } - - node := scanToLine(fileset, astFile, lineNum) - if node == nil { - return nil, errors.Errorf( - "failed to find an expression on line %d in %s", lineNum, filename) - } - return node, nil -} - -func scanToLine(fileset *token.FileSet, node ast.Node, lineNum int) ast.Node { - v := &scanToLineVisitor{lineNum: lineNum, fileset: fileset} - ast.Walk(v, node) - return v.matchedNode -} - -type scanToLineVisitor struct { - lineNum int - matchedNode ast.Node - fileset *token.FileSet -} - -func (v *scanToLineVisitor) Visit(node ast.Node) ast.Visitor { - if node == nil || v.matchedNode != nil { - return nil - } - if v.nodePosition(node).Line == v.lineNum { - v.matchedNode = node - return nil - } - return v -} - -// In golang 1.9 the line number changed from being the line where the statement -// ended to the line where the statement began. -func (v *scanToLineVisitor) nodePosition(node ast.Node) token.Position { - if goVersionBefore19 { - return v.fileset.Position(node.End()) - } - return v.fileset.Position(node.Pos()) -} - -var goVersionBefore19 = isGOVersionBefore19() - -func isGOVersionBefore19() bool { - version := runtime.Version() - // not a release version - if !strings.HasPrefix(version, "go") { - return false - } - version = strings.TrimPrefix(version, "go") - parts := strings.Split(version, ".") - if len(parts) < 2 { - return false - } - minor, err := strconv.ParseInt(parts[1], 10, 32) - return err == nil && parts[0] == "1" && minor < 9 -} - -func getCallExprArgs(node ast.Node) ([]ast.Expr, error) { - visitor := &callExprVisitor{} - ast.Walk(visitor, node) - if visitor.expr == nil { - return nil, errors.New("failed to find call expression") - } - return visitor.expr.Args, nil -} - -type callExprVisitor struct { - expr *ast.CallExpr -} - -func (v *callExprVisitor) Visit(node ast.Node) ast.Visitor { - if v.expr != nil || node == nil { - return nil - } - debug("visit (%T): %s", node, debugFormatNode{node}) - - if callExpr, ok := node.(*ast.CallExpr); ok { - v.expr = callExpr - return nil - } - return v -} - -// FormatNode using go/format.Node and return the result as a string -func FormatNode(node ast.Node) (string, error) { - buf := new(bytes.Buffer) - err := format.Node(buf, token.NewFileSet(), node) - return buf.String(), err -} - // CallExprArgs returns the ast.Expr slice for the args of an ast.CallExpr at // the index in the call stack. func CallExprArgs(stackIndex int) ([]ast.Expr, error) { @@ -137,12 +43,109 @@ func CallExprArgs(stackIndex int) ([]ast.Expr, error) { if err != nil { return nil, err } - debug("found node (%T): %s", node, debugFormatNode{node}) + debug("found node: %s", debugFormatNode{node}) return getCallExprArgs(node) } -var debugEnabled = os.Getenv("GOTESTYOURSELF_DEBUG") != "" +func getNodeAtLine(filename string, lineNum int) (ast.Node, error) { + fileset := token.NewFileSet() + astFile, err := parser.ParseFile(fileset, filename, nil, parser.AllErrors) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse source file: %s", filename) + } + + if node := scanToLine(fileset, astFile, lineNum); node != nil { + return node, nil + } + if node := scanToDeferLine(fileset, astFile, lineNum); node != nil { + node, err := guessDefer(node) + if err != nil || node != nil { + return node, err + } + } + return nil, errors.Errorf( + "failed to find an expression on line %d in %s", lineNum, filename) +} + +func scanToLine(fileset *token.FileSet, node ast.Node, lineNum int) ast.Node { + var matchedNode ast.Node + ast.Inspect(node, func(node ast.Node) bool { + switch { + case node == nil || matchedNode != nil: + return false + case nodePosition(fileset, node).Line == lineNum: + matchedNode = node + return false + } + return true + }) + return matchedNode +} + +// In golang 1.9 the line number changed from being the line where the statement +// ended to the line where the statement began. +func nodePosition(fileset *token.FileSet, node ast.Node) token.Position { + if goVersionBefore19 { + return fileset.Position(node.End()) + } + return fileset.Position(node.Pos()) +} + +var goVersionBefore19 = func() bool { + version := runtime.Version() + // not a release version + if !strings.HasPrefix(version, "go") { + return false + } + version = strings.TrimPrefix(version, "go") + parts := strings.Split(version, ".") + if len(parts) < 2 { + return false + } + minor, err := strconv.ParseInt(parts[1], 10, 32) + return err == nil && parts[0] == "1" && minor < 9 +}() + +func getCallExprArgs(node ast.Node) ([]ast.Expr, error) { + visitor := &callExprVisitor{} + ast.Walk(visitor, node) + if visitor.expr == nil { + return nil, errors.New("failed to find call expression") + } + debug("callExpr: %s", debugFormatNode{visitor.expr}) + return visitor.expr.Args, nil +} + +type callExprVisitor struct { + expr *ast.CallExpr +} + +func (v *callExprVisitor) Visit(node ast.Node) ast.Visitor { + if v.expr != nil || node == nil { + return nil + } + debug("visit: %s", debugFormatNode{node}) + + switch typed := node.(type) { + case *ast.CallExpr: + v.expr = typed + return nil + case *ast.DeferStmt: + ast.Walk(v, typed.Call.Fun) + return nil + } + return v +} + +// FormatNode using go/format.Node and return the result as a string +func FormatNode(node ast.Node) (string, error) { + buf := new(bytes.Buffer) + err := format.Node(buf, token.NewFileSet(), node) + return buf.String(), err +} + +var debugEnabled = os.Getenv("GOTESTTOOLS_DEBUG") != "" func debug(format string, args ...interface{}) { if debugEnabled { @@ -159,5 +162,5 @@ func (n debugFormatNode) String() string { if err != nil { return fmt.Sprintf("failed to format %s: %s", n.Node, err) } - return out + return fmt.Sprintf("(%T) %s", n.Node, out) } diff --git a/libnetwork/vendor/gotest.tools/poll/check.go b/libnetwork/vendor/gotest.tools/poll/check.go new file mode 100644 index 0000000000..060b099890 --- /dev/null +++ b/libnetwork/vendor/gotest.tools/poll/check.go @@ -0,0 +1,39 @@ +package poll + +import ( + "net" + "os" +) + +// Check is a function which will be used as check for the WaitOn method. +type Check func(t LogT) Result + +// FileExists looks on filesystem and check that path exists. +func FileExists(path string) Check { + return func(t LogT) Result { + _, err := os.Stat(path) + if os.IsNotExist(err) { + t.Logf("waiting on file %s to exist", path) + return Continue("file %s does not exist", path) + } + if err != nil { + return Error(err) + } + + return Success() + } +} + +// Connection try to open a connection to the address on the +// named network. See net.Dial for a description of the network and +// address parameters. +func Connection(network, address string) Check { + return func(t LogT) Result { + _, err := net.Dial(network, address) + if err != nil { + t.Logf("waiting on socket %s://%s to be available...", network, address) + return Continue("socket %s://%s not available", network, address) + } + return Success() + } +} diff --git a/libnetwork/vendor/gotest.tools/poll/poll.go b/libnetwork/vendor/gotest.tools/poll/poll.go new file mode 100644 index 0000000000..5e238dca42 --- /dev/null +++ b/libnetwork/vendor/gotest.tools/poll/poll.go @@ -0,0 +1,140 @@ +/*Package poll provides tools for testing asynchronous code. + */ +package poll // import "gotest.tools/poll" + +import ( + "fmt" + "time" +) + +// TestingT is the subset of testing.T used by WaitOn +type TestingT interface { + LogT + Fatalf(format string, args ...interface{}) +} + +// LogT is a logging interface that is passed to the WaitOn check function +type LogT interface { + Log(args ...interface{}) + Logf(format string, args ...interface{}) +} + +type helperT interface { + Helper() +} + +// Settings are used to configure the behaviour of WaitOn +type Settings struct { + // Timeout is the maximum time to wait for the condition. Defaults to 10s. + Timeout time.Duration + // Delay is the time to sleep between checking the condition. Defaults to + // 100ms. + Delay time.Duration +} + +func defaultConfig() *Settings { + return &Settings{Timeout: 10 * time.Second, Delay: 100 * time.Millisecond} +} + +// SettingOp is a function which accepts and modifies Settings +type SettingOp func(config *Settings) + +// WithDelay sets the delay to wait between polls +func WithDelay(delay time.Duration) SettingOp { + return func(config *Settings) { + config.Delay = delay + } +} + +// WithTimeout sets the timeout +func WithTimeout(timeout time.Duration) SettingOp { + return func(config *Settings) { + config.Timeout = timeout + } +} + +// Result of a check performed by WaitOn +type Result interface { + // Error indicates that the check failed and polling should stop, and the + // the has failed + Error() error + // Done indicates that polling should stop, and the test should proceed + Done() bool + // Message provides the most recent state when polling has not completed + Message() string +} + +type result struct { + done bool + message string + err error +} + +func (r result) Done() bool { + return r.done +} + +func (r result) Message() string { + return r.message +} + +func (r result) Error() error { + return r.err +} + +// Continue returns a Result that indicates to WaitOn that it should continue +// polling. The message text will be used as the failure message if the timeout +// is reached. +func Continue(message string, args ...interface{}) Result { + return result{message: fmt.Sprintf(message, args...)} +} + +// Success returns a Result where Done() returns true, which indicates to WaitOn +// that it should stop polling and exit without an error. +func Success() Result { + return result{done: true} +} + +// Error returns a Result that indicates to WaitOn that it should fail the test +// and stop polling. +func Error(err error) Result { + return result{err: err} +} + +// WaitOn a condition or until a timeout. Poll by calling check and exit when +// check returns a done Result. To fail a test and exit polling with an error +// return a error result. +func WaitOn(t TestingT, check Check, pollOps ...SettingOp) { + if ht, ok := t.(helperT); ok { + ht.Helper() + } + config := defaultConfig() + for _, pollOp := range pollOps { + pollOp(config) + } + + var lastMessage string + after := time.After(config.Timeout) + chResult := make(chan Result) + for { + go func() { + chResult <- check(t) + }() + select { + case <-after: + if lastMessage == "" { + lastMessage = "first check never completed" + } + t.Fatalf("timeout hit after %s: %s", config.Timeout, lastMessage) + case result := <-chResult: + switch { + case result.Error() != nil: + t.Fatalf("polling check failed: %s", result.Error()) + case result.Done(): + return + } + time.Sleep(config.Delay) + lastMessage = result.Message() + } + } +}