summaryrefslogtreecommitdiff
path: root/vendor/github.com/getlantern
diff options
context:
space:
mode:
authorKali Kaneko (leap communications) <kali@leap.se>2019-01-12 18:39:45 +0100
committerRuben Pollan <meskio@sindominio.net>2019-01-17 12:30:32 +0100
commitb1247d2d0d51108c910a73891ff3116e5f032ab1 (patch)
treee9948964f0bfb1ad2df3bc7bad02aa1f41ccfbd8 /vendor/github.com/getlantern
parentefcb8312e31b5c2261b1a1e95ace55b322cfcc27 (diff)
[pkg] all your deps are vendored to us
Diffstat (limited to 'vendor/github.com/getlantern')
-rw-r--r--vendor/github.com/getlantern/context/LICENSE202
-rw-r--r--vendor/github.com/getlantern/context/README.md6
-rw-r--r--vendor/github.com/getlantern/context/context.go309
-rw-r--r--vendor/github.com/getlantern/context/context_test.go160
-rw-r--r--vendor/github.com/getlantern/context/gotrack.go130
-rw-r--r--vendor/github.com/getlantern/errors/errors.go566
-rw-r--r--vendor/github.com/getlantern/errors/errors_test.go142
-rw-r--r--vendor/github.com/getlantern/errors/hide.go50
-rw-r--r--vendor/github.com/getlantern/golog/LICENSE202
-rw-r--r--vendor/github.com/getlantern/golog/README.md6
-rw-r--r--vendor/github.com/getlantern/golog/golog.go458
-rw-r--r--vendor/github.com/getlantern/golog/golog_test.go224
-rw-r--r--vendor/github.com/getlantern/hex/hex.go107
-rw-r--r--vendor/github.com/getlantern/hidden/hidden.go66
-rw-r--r--vendor/github.com/getlantern/hidden/hidden_test.go33
-rw-r--r--vendor/github.com/getlantern/ops/ops.go154
-rw-r--r--vendor/github.com/getlantern/ops/ops_test.go76
17 files changed, 2891 insertions, 0 deletions
diff --git a/vendor/github.com/getlantern/context/LICENSE b/vendor/github.com/getlantern/context/LICENSE
new file mode 100644
index 0000000..5dc6c26
--- /dev/null
+++ b/vendor/github.com/getlantern/context/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 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 2016 Brave New Software Project, Inc.
+
+ 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.
diff --git a/vendor/github.com/getlantern/context/README.md b/vendor/github.com/getlantern/context/README.md
new file mode 100644
index 0000000..978c9e0
--- /dev/null
+++ b/vendor/github.com/getlantern/context/README.md
@@ -0,0 +1,6 @@
+# context [![Travis CI Status](https://travis-ci.org/getlantern/context.svg?branch=master)](https://travis-ci.org/getlantern/context)&nbsp;[![Coverage Status](https://coveralls.io/repos/getlantern/context/badge.png?branch=master)](https://coveralls.io/r/getlantern/context)
+
+Provides goroutine-based context state inspired by https://github.com/tylerb/gls
+and https://github.com/jtolds/gls. It uses the same basic hack as tylerb's
+library, but adds a stack abstraction that allows nested contexts similar to
+jtolds' library, but using `Enter()` and `Exit()` instead of callback functions.
diff --git a/vendor/github.com/getlantern/context/context.go b/vendor/github.com/getlantern/context/context.go
new file mode 100644
index 0000000..c77f129
--- /dev/null
+++ b/vendor/github.com/getlantern/context/context.go
@@ -0,0 +1,309 @@
+// Package context provides a mechanism for transparently tracking contextual
+// state associated to the current goroutine and even across goroutines.
+package context
+
+import (
+ "sync"
+)
+
+// Manager provides the ability to create and access Contexts.
+type Manager interface {
+ // Enter enters a new level on the current Context stack, creating a new Context
+ // if necessary.
+ Enter() Context
+
+ // Go starts the given function on a new goroutine but sharing the context of
+ // the current goroutine (if it has one).
+ Go(func())
+
+ // PutGlobal puts the given key->value pair into the global context.
+ PutGlobal(key string, value interface{})
+
+ // PutGlobalDynamic puts a key->value pair into the global context where the
+ // value is generated by a function that gets evaluated at every Read. If the
+ // value is a map[string]interface{}, we will unpack the map and set each
+ // contained key->value pair independently.
+ PutGlobalDynamic(key string, valueFN func() interface{})
+
+ // AsMap returns a map containing all values from the supplied obj if it is a
+ // Contextual, plus any addition values from along the stack, plus globals if so
+ // specified.
+ AsMap(obj interface{}, includeGlobals bool) Map
+}
+
+type manager struct {
+ contexts map[uint64]*context
+ mxContexts sync.RWMutex
+ global Map
+ mxGlobal sync.RWMutex
+}
+
+// NewManager creates a new Manager
+func NewManager() Manager {
+ return &manager{
+ contexts: make(map[uint64]*context),
+ global: make(Map),
+ }
+}
+
+// Contextual is an interface for anything that maintains its own context.
+type Contextual interface {
+ // Fill fills the given Map with all of this Contextual's context
+ Fill(m Map)
+}
+
+// Map is a map of key->value pairs.
+type Map map[string]interface{}
+
+// Fill implements the method from the Contextual interface.
+func (_m Map) Fill(m Map) {
+ for key, value := range _m {
+ m[key] = value
+ }
+}
+
+// Context is a context containing key->value pairs
+type Context interface {
+ // Enter enters a new level on this Context stack.
+ Enter() Context
+
+ // Go starts the given function on a new goroutine.
+ Go(fn func())
+
+ // Exit exits the current level on this Context stack.
+ Exit()
+
+ // Put puts a key->value pair into the current level of the context stack.
+ Put(key string, value interface{}) Context
+
+ // PutIfAbsent puts the given key->value pair into the current level of the
+ // context stack if and only if that key is defined nowhere within the context
+ // stack (including parent contexts).
+ PutIfAbsent(key string, value interface{}) Context
+
+ // PutDynamic puts a key->value pair into the current level of the context
+ // stack where the value is generated by a function that gets evaluated at
+ // every Read. If the value is a map[string]interface{}, we will unpack the
+ // map and set each contained key->value pair independently.
+ PutDynamic(key string, valueFN func() interface{}) Context
+
+ // Fill fills the given map with data from this Context
+ Fill(m Map)
+
+ // AsMap returns a map containing all values from the supplied obj if it is a
+ // Contextual, plus any addition values from along the stack, plus globals if
+ // so specified.
+ AsMap(obj interface{}, includeGlobals bool) Map
+}
+
+type context struct {
+ cm *manager
+ id uint64
+ parent *context
+ branchedFrom *context
+ data Map
+ mx sync.RWMutex
+}
+
+type dynval struct {
+ fn func() interface{}
+}
+
+func (cm *manager) Enter() Context {
+ return cm.enter(curGoroutineID())
+}
+
+func (cm *manager) enter(id uint64) *context {
+ cm.mxContexts.Lock()
+ parentOrNil := cm.contexts[id]
+ c := cm.makeContext(id, parentOrNil, nil)
+ cm.contexts[id] = c
+ cm.mxContexts.Unlock()
+ return c
+}
+
+func (cm *manager) exit(id uint64, parent *context) {
+ cm.mxContexts.Lock()
+ if parent == nil {
+ delete(cm.contexts, id)
+ } else {
+ cm.contexts[id] = parent
+ }
+ cm.mxContexts.Unlock()
+}
+
+func (cm *manager) branch(id uint64, from *context) {
+ next := cm.makeContext(id, nil, from)
+ cm.mxContexts.Lock()
+ cm.contexts[id] = next
+ cm.mxContexts.Unlock()
+}
+
+func (cm *manager) merge(id uint64) {
+ cm.mxContexts.Lock()
+ delete(cm.contexts, id)
+ cm.mxContexts.Unlock()
+}
+
+func (c *context) Enter() Context {
+ c.mx.RLock()
+ id := c.id
+ c.mx.RUnlock()
+ return c.cm.enter(id)
+}
+
+func (c *context) Go(fn func()) {
+ go func() {
+ id := curGoroutineID()
+ c.cm.branch(id, c)
+ fn()
+ c.cm.merge(id)
+ }()
+}
+
+func (cm *manager) Go(fn func()) {
+ c := cm.currentContext()
+ if c != nil {
+ c.Go(fn)
+ } else {
+ go fn()
+ }
+}
+
+func (cm *manager) makeContext(id uint64, parent *context, branchedFrom *context) *context {
+ return &context{
+ cm: cm,
+ id: id,
+ parent: parent,
+ branchedFrom: branchedFrom,
+ data: make(Map),
+ }
+}
+
+func (c *context) Exit() {
+ c.mx.RLock()
+ id := c.id
+ parent := c.parent
+ c.mx.RUnlock()
+ c.cm.exit(id, parent)
+}
+
+func (c *context) Put(key string, value interface{}) Context {
+ c.mx.Lock()
+ c.data[key] = value
+ c.mx.Unlock()
+ return c
+}
+
+func (c *context) PutIfAbsent(key string, value interface{}) Context {
+ for ctx := c; ctx != nil; {
+ ctx.mx.RLock()
+ _, exists := ctx.data[key]
+ next := ctx.parent
+ if next == nil {
+ next = ctx.branchedFrom
+ }
+ ctx.mx.RUnlock()
+ if exists {
+ return c
+ }
+ ctx = next
+ }
+
+ // Value not set, set it
+ return c.Put(key, value)
+}
+
+func (c *context) PutDynamic(key string, valueFN func() interface{}) Context {
+ value := &dynval{valueFN}
+ c.mx.Lock()
+ c.data[key] = value
+ c.mx.Unlock()
+ return c
+}
+
+func (cm *manager) PutGlobal(key string, value interface{}) {
+ cm.mxGlobal.Lock()
+ cm.global[key] = value
+ cm.mxGlobal.Unlock()
+}
+
+func (cm *manager) PutGlobalDynamic(key string, valueFN func() interface{}) {
+ value := &dynval{valueFN}
+ cm.mxGlobal.Lock()
+ cm.global[key] = value
+ cm.mxGlobal.Unlock()
+}
+
+func (c *context) Fill(m Map) {
+ for ctx := c; ctx != nil; {
+ ctx.mx.RLock()
+ fill(m, ctx.data)
+ next := ctx.parent
+ if next == nil {
+ next = ctx.branchedFrom
+ }
+ ctx.mx.RUnlock()
+ ctx = next
+ }
+}
+
+func (cm *manager) AsMap(obj interface{}, includeGlobals bool) Map {
+ return cm.currentContext().asMap(cm, obj, includeGlobals)
+}
+
+func (c *context) AsMap(obj interface{}, includeGlobals bool) Map {
+ return c.asMap(c.cm, obj, includeGlobals)
+}
+
+func (c *context) asMap(cm *manager, obj interface{}, includeGlobals bool) Map {
+ result := make(Map, 0)
+ cl, ok := obj.(Contextual)
+ if ok {
+ cl.Fill(result)
+ }
+ if c != nil {
+ c.Fill(result)
+ }
+ if includeGlobals {
+ cm.mxGlobal.RLock()
+ fill(result, cm.global)
+ cm.mxGlobal.RUnlock()
+ }
+ return result
+}
+
+func fill(m Map, from Map) {
+ if m != nil {
+ doFill := func(key string, _value interface{}) {
+ switch value := _value.(type) {
+ case map[string]interface{}:
+ for k, v := range value {
+ m[k] = v
+ }
+ default:
+ m[key] = value
+ }
+ }
+
+ for key, value := range from {
+ _, alreadyRead := m[key]
+ if !alreadyRead {
+ switch v := value.(type) {
+ case *dynval:
+ doFill(key, v.fn())
+ default:
+ doFill(key, v)
+ }
+ }
+ }
+ }
+}
+
+func (cm *manager) currentContext() *context {
+ id := curGoroutineID()
+ cm.mxContexts.RLock()
+ c := cm.contexts[id]
+ cm.mxContexts.RUnlock()
+ return c
+}
diff --git a/vendor/github.com/getlantern/context/context_test.go b/vendor/github.com/getlantern/context/context_test.go
new file mode 100644
index 0000000..9fb86f3
--- /dev/null
+++ b/vendor/github.com/getlantern/context/context_test.go
@@ -0,0 +1,160 @@
+package context
+
+import (
+ "sync"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestStack(t *testing.T) {
+ cm := NewManager()
+ _cm := cm.(*manager)
+ // Put globals first
+ cm.PutGlobal("a", -1) // This will get overriden in specific contexts
+ cm.PutGlobal("ga", "i")
+ cm.PutGlobalDynamic("gb", func() interface{} { return "ii" })
+ cm.PutGlobalDynamic("gm", func() interface{} { return map[string]interface{}{"gm3": "iii", "gm4": "iv"} })
+
+ // Use a Map as a Contextual
+ var contextual = Map{
+ "a": 0, // This will override whatever is in specific contexts
+ "contextual": "special",
+ }
+
+ c := cm.Enter()
+ c.Put("a", 1)
+ penultimate := cm.Enter().
+ Put("b", 2)
+ c = cm.Enter().
+ PutDynamic("c", func() interface{} { return 4 }).
+ PutIfAbsent("d", 5).
+ PutIfAbsent("a", 11)
+
+ // Put something in the penultimate context and make sure it doesn't override
+ // what's set in the ultimate context
+ penultimate.Put("c", 3)
+
+ var assertMutex sync.Mutex
+ doAssertContents := func(expected Map, actual Map, scope string) {
+ assertMutex.Lock()
+ assert.Equal(t, expected, actual, scope)
+ assertMutex.Unlock()
+ }
+
+ assertContents := func(expected Map) {
+ doAssertContents(expected, cm.AsMap(nil, false), "AsMapwith(nil, false)")
+ expected["ga"] = "i"
+ expected["gb"] = "ii"
+ expected["gm3"] = "iii"
+ expected["gm4"] = "iv"
+ _, exists := expected["a"]
+ if !exists {
+ expected["a"] = -1
+ }
+ doAssertContents(expected, cm.AsMap(nil, true), "AsMap(nil, true)")
+ expected["a"] = 0
+ expected["contextual"] = "special"
+ doAssertContents(expected, cm.AsMap(contextual, true), "AsMapWith(contextual, true)")
+ delete(expected, "ga")
+ delete(expected, "gb")
+ delete(expected, "gm3")
+ delete(expected, "gm4")
+ doAssertContents(expected, cm.AsMap(contextual, false), "AsMapWith(contextual, false)")
+ }
+
+ assertContents(Map{
+ "a": 1,
+ "b": 2,
+ "c": 4,
+ "d": 5,
+ })
+
+ var wg sync.WaitGroup
+ wg.Add(1)
+ cm.Go(func() {
+ defer cm.Enter().Put("e", 6).Exit()
+ assertContents(Map{
+ "a": 1,
+ "b": 2,
+ "c": 4,
+ "d": 5,
+ "e": 6,
+ })
+ wg.Done()
+ })
+ wg.Wait()
+
+ wg.Add(1)
+ cm.Go(func() {
+ // This goroutine doesn't Exit. Still, we shouldn't leak anything.
+ wg.Done()
+ })
+ wg.Wait()
+
+ assertContents(Map{
+ "a": 1,
+ "b": 2,
+ "c": 4,
+ "d": 5,
+ })
+
+ c.Exit()
+ c = _cm.currentContext()
+ assert.NotNil(t, c)
+ assertContents(Map{
+ "a": 1,
+ "b": 2,
+ "c": 3,
+ })
+
+ c.Exit()
+ c = _cm.currentContext()
+ assert.NotNil(t, c)
+ assertContents(Map{
+ "a": 1,
+ })
+
+ // Last exit
+ c.Exit()
+ assert.Nil(t, _cm.currentContext())
+ assertContents(Map{})
+
+ // Exit again, just for good measure
+ c.Exit()
+ assert.Nil(t, _cm.currentContext())
+ assertContents(Map{})
+
+ // Spawn a goroutine with no existing contexts
+ wg.Add(1)
+ cm.Go(func() {
+ defer cm.Enter().Put("f", 7).Exit()
+ assertContents(Map{
+ "f": 7,
+ })
+ wg.Done()
+ })
+ wg.Wait()
+
+ _cm.mxContexts.Lock()
+ assert.Empty(t, _cm.contexts, "No contexts should be left")
+ _cm.mxContexts.Unlock()
+}
+
+func BenchmarkPut(b *testing.B) {
+ cm := NewManager()
+ c := cm.Enter()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ c.Put("key", "value")
+ }
+}
+
+func BenchmarkAsMap(b *testing.B) {
+ cm := NewManager()
+ cm.Enter().Put("a", 1).Put("b", 2)
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ cm.AsMap(nil, true)
+ }
+}
diff --git a/vendor/github.com/getlantern/context/gotrack.go b/vendor/github.com/getlantern/context/gotrack.go
new file mode 100644
index 0000000..7f92346
--- /dev/null
+++ b/vendor/github.com/getlantern/context/gotrack.go
@@ -0,0 +1,130 @@
+package context
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "runtime"
+ "strconv"
+ "sync"
+)
+
+// Sourced https://github.com/bradfitz/http2/blob/dc0c5c000ec33e263612939744d51a3b68b9cece/gotrack.go
+var goroutineSpace = []byte("goroutine ")
+var littleBuf = sync.Pool{
+ New: func() interface{} {
+ buf := make([]byte, 64)
+ return &buf
+ },
+}
+
+func curGoroutineID() uint64 {
+ bp := littleBuf.Get().(*[]byte)
+ defer littleBuf.Put(bp)
+ b := *bp
+ b = b[:runtime.Stack(b, false)]
+ // Parse the 4707 out of "goroutine 4707 ["
+ b = bytes.TrimPrefix(b, goroutineSpace)
+ i := bytes.IndexByte(b, ' ')
+ if i < 0 {
+ panic(fmt.Sprintf("No space found in %q", b))
+ }
+ b = b[:i]
+ n, err := parseUintBytes(b, 10, 64)
+ if err != nil {
+ panic(fmt.Sprintf("Failed to parse goroutine ID out of %q: %v", b, err))
+ }
+ return n
+}
+
+// parseUintBytes is like strconv.ParseUint, but using a []byte.
+func parseUintBytes(s []byte, base int, bitSize int) (n uint64, err error) {
+ var cutoff, maxVal uint64
+
+ if bitSize == 0 {
+ bitSize = int(strconv.IntSize)
+ }
+
+ s0 := s
+ switch {
+ case len(s) < 1:
+ err = strconv.ErrSyntax
+ return n, &strconv.NumError{Func: "ParseUint", Num: string(s0), Err: err}
+
+ case 2 <= base && base <= 36:
+ // valid base; nothing to do
+
+ case base == 0:
+ // Look for octal, hex prefix.
+ switch {
+ case s[0] == '0' && len(s) > 1 && (s[1] == 'x' || s[1] == 'X'):
+ base = 16
+ s = s[2:]
+ if len(s) < 1 {
+ err = strconv.ErrSyntax
+ return n, &strconv.NumError{Func: "ParseUint", Num: string(s0), Err: err}
+ }
+ case s[0] == '0':
+ base = 8
+ default:
+ base = 10
+ }
+
+ default:
+ err = errors.New("invalid base " + strconv.Itoa(base))
+ return n, &strconv.NumError{Func: "ParseUint", Num: string(s0), Err: err}
+ }
+
+ n = 0
+ cutoff = cutoff64(base)
+ maxVal = 1<<uint(bitSize) - 1
+
+ for i := 0; i < len(s); i++ {
+ var v byte
+ d := s[i]
+ switch {
+ case '0' <= d && d <= '9':
+ v = d - '0'
+ case 'a' <= d && d <= 'z':
+ v = d - 'a' + 10
+ case 'A' <= d && d <= 'Z':
+ v = d - 'A' + 10
+ default:
+ n = 0
+ err = strconv.ErrSyntax
+ return n, &strconv.NumError{Func: "ParseUint", Num: string(s0), Err: err}
+ }
+ if int(v) >= base {
+ n = 0
+ err = strconv.ErrSyntax
+ return n, &strconv.NumError{Func: "ParseUint", Num: string(s0), Err: err}
+ }
+
+ if n >= cutoff {
+ // n*base overflows
+ n = 1<<64 - 1
+ err = strconv.ErrRange
+ return n, &strconv.NumError{Func: "ParseUint", Num: string(s0), Err: err}
+ }
+ n *= uint64(base)
+
+ n1 := n + uint64(v)
+ if n1 < n || n1 > maxVal {
+ // n+v overflows
+ n = 1<<64 - 1
+ err = strconv.ErrRange
+ return n, &strconv.NumError{Func: "ParseUint", Num: string(s0), Err: err}
+ }
+ n = n1
+ }
+
+ return n, nil
+}
+
+// Return the first number n such that n*base >= 1<<64.
+func cutoff64(base int) uint64 {
+ if base < 2 {
+ return 0
+ }
+ return (1<<64-1)/uint64(base) + 1
+}
diff --git a/vendor/github.com/getlantern/errors/errors.go b/vendor/github.com/getlantern/errors/errors.go
new file mode 100644
index 0000000..8b2b84f
--- /dev/null
+++ b/vendor/github.com/getlantern/errors/errors.go
@@ -0,0 +1,566 @@
+/*
+Package errors defines error types used across Lantern project.
+
+ n, err := Foo()
+ if err != nil {
+ return n, errors.New("Unable to do Foo: %v", err)
+ }
+
+or
+
+ n, err := Foo()
+ return n, errors.Wrap(err)
+
+New() method will create a new error with err as its cause. Wrap will wrap err,
+returning nil if err is nil. If err is an error from Go's standard library,
+errors will extract details from that error, at least the Go type name and the
+return value of err.Error().
+
+One can record the operation on which the error occurred using Op():
+
+ return n, errors.New("Unable to do Foo: %v", err).Op("FooDooer")
+
+One can also record additional data:
+
+ return n, errors.
+ New("Unable to do Foo: %v", err).
+ Op("FooDooer").
+ With("mydata", "myvalue").
+ With("moredata", 5)
+
+When used with github.com/getlantern/ops, Error captures its current context
+and propagates that data for use in calling layers.
+
+When used with github.com/getlantern/golog, Error provides stacktraces:
+
+ Hello World
+ at github.com/getlantern/errors.TestNewWithCause (errors_test.go:999)
+ at testing.tRunner (testing.go:999)
+ at runtime.goexit (asm_amd999.s:999)
+ Caused by: World
+ at github.com/getlantern/errors.buildCause (errors_test.go:999)
+ at github.com/getlantern/errors.TestNewWithCause (errors_test.go:999)
+ at testing.tRunner (testing.go:999)
+ at runtime.goexit (asm_amd999.s:999)
+ Caused by: orld
+ Caused by: ld
+ at github.com/getlantern/errors.buildSubSubCause (errors_test.go:999)
+ at github.com/getlantern/errors.buildSubCause (errors_test.go:999)
+ at github.com/getlantern/errors.buildCause (errors_test.go:999)
+ at github.com/getlantern/errors.TestNewWithCause (errors_test.go:999)
+ at testing.tRunner (testing.go:999)
+ at runtime.goexit (asm_amd999.s:999)
+ Caused by: d
+
+It's the caller's responsibility to avoid race conditions accessing the same
+error instance from multiple goroutines.
+*/
+package errors
+
+import (
+ "bufio"
+ "bytes"
+ "crypto/tls"
+ "crypto/x509"
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net"
+ "net/http"
+ "net/textproto"
+ "net/url"
+ "os"
+ "os/exec"
+ "reflect"
+ "runtime"
+ "strconv"
+ "strings"
+ "syscall"
+ "time"
+ "unicode"
+
+ "github.com/getlantern/context"
+ "github.com/getlantern/hidden"
+ "github.com/getlantern/ops"
+ "github.com/go-stack/stack"
+)
+
+// Error wraps system and application defined errors in unified structure for
+// reporting and logging. It's not meant to be created directly. User New(),
+// Wrap() and Report() instead.
+type Error interface {
+ error
+ context.Contextual
+
+ // ErrorClean returns a non-parameterized version of the error whenever
+ // possible. For example, if the error text is:
+ //
+ // unable to dial www.google.com caused by: i/o timeout
+ //
+ // ErrorClean might return:
+ //
+ // unable to dial %v caused by: %v
+ //
+ // This can be useful when performing analytics on the error.
+ ErrorClean() string
+
+ // MultiLinePrinter implements the interface golog.MultiLine
+ MultiLinePrinter() func(buf *bytes.Buffer) bool
+
+ // Op attaches a hint of the operation triggers this Error. Many error types
+ // returned by net and os package have Op pre-filled.
+ Op(op string) Error
+
+ // With attaches arbitrary field to the error. keys will be normalized as
+ // underscore_divided_words, so all characters except letters and numbers will
+ // be replaced with underscores, and all letters will be lowercased.
+ With(key string, value interface{}) Error
+
+ // RootCause returns the bottom-most cause of this Error. If the Error
+ // resulted from wrapping a plain error, the wrapped error will be returned as
+ // the cause.
+ RootCause() error
+}
+
+type structured struct {
+ id uint64
+ hiddenID string
+ data context.Map
+ context context.Map
+ wrapped error
+ cause Error
+ callStack stack.CallStack
+}
+
+// New creates an Error with supplied description and format arguments to the
+// description. If any of the arguments is an error, we use that as the cause.
+func New(desc string, args ...interface{}) Error {
+ return NewOffset(1, desc, args...)
+}
+
+// NewOffset is like New but offsets the stack by the given offset. This is
+// useful for utilities like golog that may create errors on behalf of others.
+func NewOffset(offset int, desc string, args ...interface{}) Error {
+ var cause error
+ for _, arg := range args {
+ err, isError := arg.(error)
+ if isError {
+ cause = err
+ break
+ }
+ }
+ e := buildError(desc, fmt.Sprintf(desc, args...), nil, Wrap(cause))
+ e.attachStack(2 + offset)
+ return e
+}
+
+// Wrap creates an Error based on the information in an error instance. It
+// returns nil if the error passed in is nil, so we can simply call
+// errors.Wrap(s.l.Close()) regardless there's an error or not. If the error is
+// already wrapped, it is returned as is.
+func Wrap(err error) Error {
+ return wrapSkipFrames(err, 1)
+}
+
+// Fill implements the method from the context.Contextual interface.
+func (e *structured) Fill(m context.Map) {
+ if e != nil {
+ if e.cause != nil {
+ // Include data from cause, which supercedes context
+ e.cause.Fill(m)
+ }
+ // Include the context, which supercedes the cause
+ for key, value := range e.context {
+ m[key] = value
+ }
+ // Now include the error's data, which supercedes everything
+ for key, value := range e.data {
+ m[key] = value
+ }
+ }
+}
+
+func (e *structured) Op(op string) Error {
+ e.data["error_op"] = op
+ return e
+}
+
+func (e *structured) With(key string, value interface{}) Error {
+ parts := strings.FieldsFunc(key, func(c rune) bool {
+ return !unicode.IsLetter(c) && !unicode.IsNumber(c)
+ })
+ k := strings.ToLower(strings.Join(parts, "_"))
+ if k == "error" || k == "error_op" {
+ // Never overwrite these
+ return e
+ }
+ switch actual := value.(type) {
+ case string, int, bool, time.Time:
+ e.data[k] = actual
+ default:
+ e.data[k] = fmt.Sprint(actual)
+ }
+ return e
+}
+
+func (e *structured) RootCause() error {
+ if e.cause == nil {
+ if e.wrapped != nil {
+ return e.wrapped
+ }
+ return e
+ }
+ return e.cause.RootCause()
+}
+
+func (e *structured) ErrorClean() string {
+ return e.data["error"].(string)
+}
+
+// Error satisfies the error interface
+func (e *structured) Error() string {
+ return e.data["error_text"].(string) + e.hiddenID
+}
+
+func (e *structured) MultiLinePrinter() func(buf *bytes.Buffer) bool {
+ first := true
+ indent := false
+ err := e
+ stackPosition := 0
+ switchedCause := false
+ return func(buf *bytes.Buffer) bool {
+ if indent {
+ buf.WriteString(" ")
+ }
+ if first {
+ buf.WriteString(e.Error())
+ first = false
+ indent = true
+ return true
+ }
+ if switchedCause {
+ fmt.Fprintf(buf, "Caused by: %v", err)
+ if err.callStack != nil && len(err.callStack) > 0 {
+ switchedCause = false
+ indent = true
+ return true
+ }
+ if err.cause == nil {
+ return false
+ }
+ err = err.cause.(*structured)
+ return true
+ }
+ if stackPosition < len(err.callStack) {
+ buf.WriteString("at ")
+ call := err.callStack[stackPosition]
+ fmt.Fprintf(buf, "%+n (%s:%d)", call, call, call)
+ stackPosition++
+ }
+ if stackPosition >= len(err.callStack) {
+ switch cause := err.cause.(type) {
+ case *structured:
+ err = cause
+ indent = false
+ stackPosition = 0
+ switchedCause = true
+ default:
+ return false
+ }
+ }
+ return err != nil
+ }
+}
+
+func wrapSkipFrames(err error, skip int) Error {
+ if err == nil {
+ return nil
+ }
+
+ // Look for *structureds
+ if e, ok := err.(*structured); ok {
+ return e
+ }
+
+ var cause Error
+ // Look for hidden *structureds
+ hiddenIDs, err2 := hidden.Extract(err.Error())
+ if err2 == nil && len(hiddenIDs) > 0 {
+ // Take the first hidden ID as our cause
+ cause = get(hiddenIDs[0])
+ }
+
+ // Create a new *structured
+ return buildError("", "", err, cause)
+}
+
+func (e *structured) attachStack(skip int) {
+ call := stack.Caller(skip)
+ e.callStack = stack.Trace().TrimBelow(call)
+ e.data["error_location"] = fmt.Sprintf("%+n (%s:%d)", call, call, call)
+}
+
+func buildError(desc string, fullText string, wrapped error, cause Error) *structured {
+ e := &structured{
+ data: make(context.Map),
+ // We capture the current context to allow it to propagate to higher layers.
+ context: ops.AsMap(nil, false),
+ wrapped: wrapped,
+ cause: cause,
+ }
+ e.save()
+
+ errorType := "errors.Error"
+ if wrapped != nil {
+ op, goType, wrappedDesc, extra := parseError(wrapped)
+ if desc == "" {
+ desc = wrappedDesc
+ }
+ e.Op(op)
+ errorType = goType
+ if extra != nil {
+ for key, value := range extra {
+ e.data[key] = value
+ }
+ }
+ }
+
+ cleanedDesc := hidden.Clean(desc)
+ e.data["error"] = cleanedDesc
+ if fullText != "" {
+ e.data["error_text"] = hidden.Clean(fullText)
+ } else {
+ e.data["error_text"] = cleanedDesc
+ }
+ e.data["error_type"] = errorType
+
+ return e
+}
+
+func parseError(err error) (op string, goType string, desc string, extra map[string]string) {
+ extra = make(map[string]string)
+
+ // interfaces
+ if _, ok := err.(net.Error); ok {
+ if opError, ok := err.(*net.OpError); ok {
+ op = opError.Op
+ if opError.Source != nil {
+ extra["remote_addr"] = opError.Source.String()
+ }
+ if opError.Addr != nil {
+ extra["local_addr"] = opError.Addr.String()
+ }
+ extra["network"] = opError.Net
+ err = opError.Err
+ }
+ switch actual := err.(type) {
+ case *net.AddrError:
+ goType = "net.AddrError"
+ desc = actual.Err
+ extra["addr"] = actual.Addr
+ case *net.DNSError:
+ goType = "net.DNSError"
+ desc = actual.Err
+ extra["domain"] = actual.Name
+ if actual.Server != "" {
+ extra["dns_server"] = actual.Server
+ }
+ case *net.InvalidAddrError:
+ goType = "net.InvalidAddrError"
+ desc = actual.Error()
+ case *net.ParseError:
+ goType = "net.ParseError"
+ desc = "invalid " + actual.Type
+ extra["text_to_parse"] = actual.Text
+ case net.UnknownNetworkError:
+ goType = "net.UnknownNetworkError"
+ desc = "unknown network"
+ case syscall.Errno:
+ goType = "syscall.Errno"
+ desc = actual.Error()
+ case *url.Error:
+ goType = "url.Error"
+ desc = actual.Err.Error()
+ op = actual.Op
+ default:
+ goType = reflect.TypeOf(err).String()
+ desc = err.Error()
+ }
+ return
+ }
+ if _, ok := err.(runtime.Error); ok {
+ desc = err.Error()
+ switch err.(type) {
+ case *runtime.TypeAssertionError:
+ goType = "runtime.TypeAssertionError"
+ default:
+ goType = reflect.TypeOf(err).String()
+ }
+ return
+ }
+
+ // structs
+ switch actual := err.(type) {
+ case *http.ProtocolError:
+ desc = actual.ErrorString
+ if name, ok := httpProtocolErrors[err]; ok {
+ goType = name
+ } else {
+ goType = "http.ProtocolError"
+ }
+ case url.EscapeError, *url.EscapeError:
+ goType = "url.EscapeError"
+ desc = "invalid URL escape"
+ case url.InvalidHostError, *url.InvalidHostError:
+ goType = "url.InvalidHostError"
+ desc = "invalid character in host name"
+ case *textproto.Error:
+ goType = "textproto.Error"
+ desc = actual.Error()
+ case textproto.ProtocolError, *textproto.ProtocolError:
+ goType = "textproto.ProtocolError"
+ desc = actual.Error()
+
+ case tls.RecordHeaderError:
+ goType = "tls.RecordHeaderError"
+ desc = actual.Msg
+ extra["header"] = hex.EncodeToString(actual.RecordHeader[:])
+ case x509.CertificateInvalidError:
+ goType = "x509.CertificateInvalidError"
+ desc = actual.Error()
+ case x509.ConstraintViolationError:
+ goType = "x509.ConstraintViolationError"
+ desc = actual.Error()
+ case x509.HostnameError:
+ goType = "x509.HostnameError"
+ desc = actual.Error()
+ extra["host"] = actual.Host
+ case x509.InsecureAlgorithmError:
+ goType = "x509.InsecureAlgorithmError"
+ desc = actual.Error()
+ case x509.SystemRootsError:
+ goType = "x509.SystemRootsError"
+ desc = actual.Error()
+ case x509.UnhandledCriticalExtension:
+ goType = "x509.UnhandledCriticalExtension"
+ desc = actual.Error()
+ case x509.UnknownAuthorityError:
+ goType = "x509.UnknownAuthorityError"
+ desc = actual.Error()
+ case hex.InvalidByteError:
+ goType = "hex.InvalidByteError"
+ desc = "invalid byte"
+ case *json.InvalidUTF8Error:
+ goType = "json.InvalidUTF8Error"
+ desc = "invalid UTF-8 in string"
+ case *json.InvalidUnmarshalError:
+ goType = "json.InvalidUnmarshalError"
+ desc = actual.Error()
+ case *json.MarshalerError:
+ goType = "json.MarshalerError"
+ desc = actual.Error()
+ case *json.SyntaxError:
+ goType = "json.SyntaxError"
+ desc = actual.Error()
+ case *json.UnmarshalFieldError:
+ goType = "json.UnmarshalFieldError"
+ desc = actual.Error()
+ case *json.UnmarshalTypeError:
+ goType = "json.UnmarshalTypeError"
+ desc = actual.Error()
+ case *json.UnsupportedTypeError:
+ goType = "json.UnsupportedTypeError"
+ desc = actual.Error()
+ case *json.UnsupportedValueError:
+ goType = "json.UnsupportedValueError"
+ desc = actual.Error()
+
+ case *os.LinkError:
+ goType = "os.LinkError"
+ desc = actual.Error()
+ case *os.PathError:
+ goType = "os.PathError"
+ op = actual.Op
+ desc = actual.Err.Error()
+ case *os.SyscallError:
+ goType = "os.SyscallError"
+ op = actual.Syscall
+ desc = actual.Err.Error()
+ case *exec.Error:
+ goType = "exec.Error"
+ desc = actual.Err.Error()
+ case *exec.ExitError:
+ goType = "exec.ExitError"
+ desc = actual.Error()
+ // TODO: limit the length
+ extra["stderr"] = string(actual.Stderr)
+ case *strconv.NumError:
+ goType = "strconv.NumError"
+ desc = actual.Err.Error()
+ extra["function"] = actual.Func
+ case *time.ParseError:
+ goType = "time.ParseError"
+ desc = actual.Message
+ default:
+ desc = err.Error()
+ if t, ok := miscErrors[err]; ok {
+ goType = t
+ return
+ }
+ goType = reflect.TypeOf(err).String()
+ }
+ return
+}
+
+var httpProtocolErrors = map[error]string{
+ http.ErrHeaderTooLong: "http.ErrHeaderTooLong",
+ http.ErrShortBody: "http.ErrShortBody",
+ http.ErrNotSupported: "http.ErrNotSupported",
+ http.ErrUnexpectedTrailer: "http.ErrUnexpectedTrailer",
+ http.ErrMissingContentLength: "http.ErrMissingContentLength",
+ http.ErrNotMultipart: "http.ErrNotMultipart",
+ http.ErrMissingBoundary: "http.ErrMissingBoundary",
+}
+
+var miscErrors = map[error]string{
+ bufio.ErrInvalidUnreadByte: "bufio.ErrInvalidUnreadByte",
+ bufio.ErrInvalidUnreadRune: "bufio.ErrInvalidUnreadRune",
+ bufio.ErrBufferFull: "bufio.ErrBufferFull",
+ bufio.ErrNegativeCount: "bufio.ErrNegativeCount",
+ bufio.ErrTooLong: "bufio.ErrTooLong",
+ bufio.ErrNegativeAdvance: "bufio.ErrNegativeAdvance",
+ bufio.ErrAdvanceTooFar: "bufio.ErrAdvanceTooFar",
+ bufio.ErrFinalToken: "bufio.ErrFinalToken",
+
+ http.ErrWriteAfterFlush: "http.ErrWriteAfterFlush",
+ http.ErrBodyNotAllowed: "http.ErrBodyNotAllowed",
+ http.ErrHijacked: "http.ErrHijacked",
+ http.ErrContentLength: "http.ErrContentLength",
+ http.ErrBodyReadAfterClose: "http.ErrBodyReadAfterClose",
+ http.ErrHandlerTimeout: "http.ErrHandlerTimeout",
+ http.ErrLineTooLong: "http.ErrLineTooLong",
+ http.ErrMissingFile: "http.ErrMissingFile",
+ http.ErrNoCookie: "http.ErrNoCookie",
+ http.ErrNoLocation: "http.ErrNoLocation",
+ http.ErrSkipAltProtocol: "http.ErrSkipAltProtocol",
+
+ io.EOF: "io.EOF",
+ io.ErrClosedPipe: "io.ErrClosedPipe",
+ io.ErrNoProgress: "io.ErrNoProgress",
+ io.ErrShortBuffer: "io.ErrShortBuffer",
+ io.ErrShortWrite: "io.ErrShortWrite",
+ io.ErrUnexpectedEOF: "io.ErrUnexpectedEOF",
+
+ os.ErrInvalid: "os.ErrInvalid",
+ os.ErrPermission: "os.ErrPermission",
+ os.ErrExist: "os.ErrExist",
+ os.ErrNotExist: "os.ErrNotExist",
+
+ exec.ErrNotFound: "exec.ErrNotFound",
+
+ x509.ErrUnsupportedAlgorithm: "x509.ErrUnsupportedAlgorithm",
+ x509.IncorrectPasswordError: "x509.IncorrectPasswordError",
+
+ hex.ErrLength: "hex.ErrLength",
+}
diff --git a/vendor/github.com/getlantern/errors/errors_test.go b/vendor/github.com/getlantern/errors/errors_test.go
new file mode 100644
index 0000000..7c4887a
--- /dev/null
+++ b/vendor/github.com/getlantern/errors/errors_test.go
@@ -0,0 +1,142 @@
+package errors
+
+import (
+ "bytes"
+ "fmt"
+ "regexp"
+ "testing"
+
+ "github.com/getlantern/context"
+ "github.com/getlantern/hidden"
+ "github.com/getlantern/ops"
+ "github.com/stretchr/testify/assert"
+)
+
+var (
+ replaceNumbers = regexp.MustCompile("[0-9]+")
+)
+
+func TestFull(t *testing.T) {
+ var firstErr Error
+
+ // Iterate past the size of the hidden buffer
+ for i := 0; i < len(hiddenErrors)*2; i++ {
+ op := ops.Begin("op1").Set("ca", 100).Set("cd", 100)
+ e := New("Hello %v", "There").Op("My Op").With("DaTa_1", 1)
+ op.End()
+ if firstErr == nil {
+ firstErr = e
+ }
+ assert.Equal(t, "Hello There", e.Error()[:11])
+ op = ops.Begin("op2").Set("ca", 200).Set("cb", 200).Set("cc", 200)
+ e3 := Wrap(fmt.Errorf("I'm wrapping your text: %v", e)).Op("outer op").With("dATA+1", i).With("cb", 300)
+ op.End()
+ assert.Equal(t, e, e3.(*structured).cause, "Wrapping a regular error should have extracted the contained *Error")
+ m := make(context.Map)
+ e3.Fill(m)
+ assert.Equal(t, i, m["data_1"], "Error's data should dominate all")
+ assert.Equal(t, 200, m["ca"], "Error's context should dominate cause")
+ assert.Equal(t, 300, m["cb"], "Error's data should dominate its context")
+ assert.Equal(t, 200, m["cc"], "Error's context should come through")
+ assert.Equal(t, 100, m["cd"], "Cause's context should come through")
+ assert.Equal(t, "My Op", e.(*structured).data["error_op"], "Op should be available from cause")
+
+ for _, call := range e3.(*structured).callStack {
+ t.Logf("at %v", call)
+ }
+ }
+
+ e3 := Wrap(fmt.Errorf("I'm wrapping your text: %v", firstErr)).With("a", 2)
+ assert.Nil(t, e3.(*structured).cause, "Wrapping an *Error that's no longer buffered should have yielded no cause")
+}
+
+func TestNewWithCause(t *testing.T) {
+ cause := buildCause()
+ outer := New("Hello %v", cause)
+ assert.Equal(t, "Hello World", hidden.Clean(outer.Error()))
+ assert.Equal(t, "Hello %v", outer.(*structured).ErrorClean())
+ assert.Equal(t,
+ "github.com/getlantern/errors.TestNewWithCause (errors_test.go:999)",
+ replaceNumbers.ReplaceAllString(outer.(*structured).data["error_location"].(string), "999"))
+ assert.Equal(t, cause, outer.(*structured).cause)
+
+ // Make sure that stacktrace prints out okay
+ buf := &bytes.Buffer{}
+ print := outer.MultiLinePrinter()
+ for {
+ more := print(buf)
+ buf.WriteByte('\n')
+ if !more {
+ break
+ }
+ }
+ expected := `Hello World
+ at github.com/getlantern/errors.TestNewWithCause (errors_test.go:999)
+ at testing.tRunner (testing.go:999)
+ at runtime.goexit (asm_amd999.s:999)
+Caused by: World
+ at github.com/getlantern/errors.buildCause (errors_test.go:999)
+ at github.com/getlantern/errors.TestNewWithCause (errors_test.go:999)
+ at testing.tRunner (testing.go:999)
+ at runtime.goexit (asm_amd999.s:999)
+Caused by: orld
+Caused by: ld
+ at github.com/getlantern/errors.buildSubSubCause (errors_test.go:999)
+ at github.com/getlantern/errors.buildSubCause (errors_test.go:999)
+ at github.com/getlantern/errors.buildCause (errors_test.go:999)
+ at github.com/getlantern/errors.TestNewWithCause (errors_test.go:999)
+ at testing.tRunner (testing.go:999)
+ at runtime.goexit (asm_amd999.s:999)
+Caused by: d
+`
+
+ assert.Equal(t, expected, replaceNumbers.ReplaceAllString(hidden.Clean(buf.String()), "999"))
+ assert.Equal(t, buildSubSubSubCause(), outer.RootCause())
+}
+
+func buildCause() Error {
+ return New("W%v", buildSubCause())
+}
+
+func buildSubCause() error {
+ return fmt.Errorf("or%v", buildSubSubCause())
+}
+
+func buildSubSubCause() error {
+ return New("l%v", buildSubSubSubCause())
+}
+
+func buildSubSubSubCause() error {
+ return fmt.Errorf("d")
+}
+
+func TestWrapNil(t *testing.T) {
+ assert.Nil(t, doWrapNil())
+}
+
+func doWrapNil() error {
+ return Wrap(nil)
+}
+
+func TestHiddenWithCause(t *testing.T) {
+ e1 := fmt.Errorf("I failed %v", "dude")
+ e2 := New("I wrap: %v", e1)
+ e3 := fmt.Errorf("Hiding %v", e2)
+ // clear hidden buffer
+ hiddenErrors = make([]*structured, 100)
+ e4 := Wrap(e3)
+ e5 := New("I'm really outer: %v", e4)
+
+ buf := &bytes.Buffer{}
+ print := e5.MultiLinePrinter()
+ for {
+ more := print(buf)
+ buf.WriteByte('\n')
+ if !more {
+ break
+ }
+ }
+ fmt.Println(buf.String())
+ // We're not asserting the output because we're just making sure that printing
+ // doesn't panic. If we get to this point without panicking, we're happy.
+}
diff --git a/vendor/github.com/getlantern/errors/hide.go b/vendor/github.com/getlantern/errors/hide.go
new file mode 100644
index 0000000..f10d863
--- /dev/null
+++ b/vendor/github.com/getlantern/errors/hide.go
@@ -0,0 +1,50 @@
+package errors
+
+import (
+ "encoding/binary"
+ "sync"
+
+ "github.com/getlantern/hidden"
+)
+
+var (
+ hiddenErrors = make([]*structured, 100)
+ nextID = uint64(0)
+ hiddenMutex sync.RWMutex
+)
+
+// This trick saves the error to a ring buffer and embeds a non-printing
+// hiddenID in the error's description, so that if the errors is later wrapped
+// by a standard error using something like
+// fmt.Errorf("An error occurred: %v", thisError), we can subsequently extract
+// the error simply using the hiddenID in the string.
+func (e *structured) save() {
+ hiddenMutex.Lock()
+ b := make([]byte, 8)
+ binary.BigEndian.PutUint64(b, nextID)
+ e.id = nextID
+ e.hiddenID = hidden.ToString(b)
+ hiddenErrors[idxForID(nextID)] = e
+ nextID++
+ hiddenMutex.Unlock()
+}
+
+func get(hiddenID []byte) Error {
+ if len(hiddenID) != 8 {
+ return nil
+ }
+ id := binary.BigEndian.Uint64(hiddenID)
+ hiddenMutex.RLock()
+ err := hiddenErrors[idxForID(id)]
+ hiddenMutex.RUnlock()
+ if err != nil && err.id == id {
+ // Found it!
+ return err
+ }
+ // buffer has rolled over
+ return nil
+}
+
+func idxForID(id uint64) int {
+ return int(id % uint64(len(hiddenErrors)))
+}
diff --git a/vendor/github.com/getlantern/golog/LICENSE b/vendor/github.com/getlantern/golog/LICENSE
new file mode 100644
index 0000000..3ee0162
--- /dev/null
+++ b/vendor/github.com/getlantern/golog/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 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 2014 Brave New Software Project, Inc.
+
+ 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.
diff --git a/vendor/github.com/getlantern/golog/README.md b/vendor/github.com/getlantern/golog/README.md
new file mode 100644
index 0000000..88fe677
--- /dev/null
+++ b/vendor/github.com/getlantern/golog/README.md
@@ -0,0 +1,6 @@
+golog [![Travis CI Status](https://travis-ci.org/getlantern/golog.svg?branch=master)](https://travis-ci.org/getlantern/golog)&nbsp;[![Coverage Status](https://coveralls.io/repos/getlantern/golog/badge.png)](https://coveralls.io/r/getlantern/golog)&nbsp;[![GoDoc](https://godoc.org/github.com/getlantern/golog?status.png)](http://godoc.org/github.com/getlantern/golog)
+==========
+Provides logging used in many getlantern components.
+
+[GoDoc](https://godoc.org/github.com/getlantern/golog)
+
diff --git a/vendor/github.com/getlantern/golog/golog.go b/vendor/github.com/getlantern/golog/golog.go
new file mode 100644
index 0000000..143e904
--- /dev/null
+++ b/vendor/github.com/getlantern/golog/golog.go
@@ -0,0 +1,458 @@
+// Package golog implements logging functions that log errors to stderr and
+// debug messages to stdout. Trace logging is also supported.
+// Trace logs go to stdout as well, but they are only written if the program
+// is run with environment variable "TRACE=true".
+// A stack dump will be printed after the message if "PRINT_STACK=true".
+package golog
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "os"
+ "path/filepath"
+ "runtime"
+ "sort"
+ "strconv"
+ "strings"
+ "sync"
+ "sync/atomic"
+
+ "github.com/getlantern/errors"
+ "github.com/getlantern/hidden"
+ "github.com/getlantern/ops"
+ "github.com/oxtoacart/bpool"
+)
+
+const (
+ // ERROR is an error Severity
+ ERROR = 500
+
+ // FATAL is an error Severity
+ FATAL = 600
+)
+
+var (
+ outs atomic.Value
+ reporters []ErrorReporter
+ reportersMutex sync.RWMutex
+
+ bufferPool = bpool.NewBufferPool(200)
+
+ onFatal atomic.Value
+)
+
+// Severity is a level of error (higher values are more severe)
+type Severity int
+
+func (s Severity) String() string {
+ switch s {
+ case ERROR:
+ return "ERROR"
+ case FATAL:
+ return "FATAL"
+ default:
+ return "UNKNOWN"
+ }
+}
+
+func init() {
+ DefaultOnFatal()
+ ResetOutputs()
+}
+
+func SetOutputs(errorOut io.Writer, debugOut io.Writer) {
+ outs.Store(&outputs{
+ ErrorOut: errorOut,
+ DebugOut: debugOut,
+ })
+}
+
+func ResetOutputs() {
+ SetOutputs(os.Stderr, os.Stdout)
+}
+
+func GetOutputs() *outputs {
+ return outs.Load().(*outputs)
+}
+
+// RegisterReporter registers the given ErrorReporter. All logged Errors are
+// sent to this reporter.
+func RegisterReporter(reporter ErrorReporter) {
+ reportersMutex.Lock()
+ reporters = append(reporters, reporter)
+ reportersMutex.Unlock()
+}
+
+// OnFatal configures golog to call the given function on any FATAL error. By
+// default, golog calls os.Exit(1) on any FATAL error.
+func OnFatal(fn func(err error)) {
+ onFatal.Store(fn)
+}
+
+// DefaultOnFatal enables the default behavior for OnFatal
+func DefaultOnFatal() {
+ onFatal.Store(func(err error) {
+ os.Exit(1)
+ })
+}
+
+type outputs struct {
+ ErrorOut io.Writer
+ DebugOut io.Writer
+}
+
+// MultiLine is an interface for arguments that support multi-line output.
+type MultiLine interface {
+ // MultiLinePrinter returns a function that can be used to print the
+ // multi-line output. The returned function writes one line to the buffer and
+ // returns true if there are more lines to write. This function does not need
+ // to take care of trailing carriage returns, golog handles that
+ // automatically.
+ MultiLinePrinter() func(buf *bytes.Buffer) bool
+}
+
+// ErrorReporter is a function to which the logger will report errors.
+// It the given error and corresponding message along with associated ops
+// context. This should return quickly as it executes on the critical code
+// path. The recommended approach is to buffer as much as possible and discard
+// new reports if the buffer becomes saturated.
+type ErrorReporter func(err error, linePrefix string, severity Severity, ctx map[string]interface{})
+
+type Logger interface {
+ // Debug logs to stdout
+ Debug(arg interface{})
+ // Debugf logs to stdout
+ Debugf(message string, args ...interface{})
+
+ // Error logs to stderr
+ Error(arg interface{}) error
+ // Errorf logs to stderr. It returns the first argument that's an error, or
+ // a new error built using fmt.Errorf if none of the arguments are errors.
+ Errorf(message string, args ...interface{}) error
+
+ // Fatal logs to stderr and then exits with status 1
+ Fatal(arg interface{})
+ // Fatalf logs to stderr and then exits with status 1
+ Fatalf(message string, args ...interface{})
+
+ // Trace logs to stderr only if TRACE=true
+ Trace(arg interface{})
+ // Tracef logs to stderr only if TRACE=true
+ Tracef(message string, args ...interface{})
+
+ // TraceOut provides access to an io.Writer to which trace information can
+ // be streamed. If running with environment variable "TRACE=true", TraceOut
+ // will point to os.Stderr, otherwise it will point to a ioutil.Discared.
+ // Each line of trace information will be prefixed with this Logger's
+ // prefix.
+ TraceOut() io.Writer
+
+ // IsTraceEnabled() indicates whether or not tracing is enabled for this
+ // logger.
+ IsTraceEnabled() bool
+
+ // AsStdLogger returns an standard logger
+ AsStdLogger() *log.Logger
+}
+
+func LoggerFor(prefix string) Logger {
+ l := &logger{
+ prefix: prefix + ": ",
+ pc: make([]uintptr, 10),
+ }
+
+ trace := os.Getenv("TRACE")
+ l.traceOn, _ = strconv.ParseBool(trace)
+ if !l.traceOn {
+ prefixes := strings.Split(trace, ",")
+ for _, p := range prefixes {
+ if prefix == strings.Trim(p, " ") {
+ l.traceOn = true
+ break
+ }
+ }
+ }
+ if l.traceOn {
+ l.traceOut = l.newTraceWriter()
+ } else {
+ l.traceOut = ioutil.Discard
+ }
+
+ printStack := os.Getenv("PRINT_STACK")
+ l.printStack, _ = strconv.ParseBool(printStack)
+
+ return l
+}
+
+type logger struct {
+ prefix string
+ traceOn bool
+ traceOut io.Writer
+ printStack bool
+ outs atomic.Value
+ pc []uintptr
+ funcForPc *runtime.Func
+}
+
+// attaches the file and line number corresponding to
+// the log message
+func (l *logger) linePrefix(skipFrames int) string {
+ runtime.Callers(skipFrames, l.pc)
+ funcForPc := runtime.FuncForPC(l.pc[0])
+ file, line := funcForPc.FileLine(l.pc[0] - 1)
+ return fmt.Sprintf("%s%s:%d ", l.prefix, filepath.Base(file), line)
+}
+
+func (l *logger) print(out io.Writer, skipFrames int, severity string, arg interface{}) string {
+ buf := bufferPool.Get()
+ defer bufferPool.Put(buf)
+
+ linePrefix := l.linePrefix(skipFrames)
+ writeHeader := func() {
+ buf.WriteString(severity)
+ buf.WriteString(" ")
+ buf.WriteString(linePrefix)
+ }
+ if arg != nil {
+ ml, isMultiline := arg.(MultiLine)
+ if !isMultiline {
+ writeHeader()
+ fmt.Fprintf(buf, "%v", arg)
+ printContext(buf, arg)
+ buf.WriteByte('\n')
+ } else {
+ mlp := ml.MultiLinePrinter()
+ first := true
+ for {
+ writeHeader()
+ more := mlp(buf)
+ if first {
+ printContext(buf, arg)
+ first = false
+ }
+ buf.WriteByte('\n')
+ if !more {
+ break
+ }
+ }
+ }
+ }
+ b := []byte(hidden.Clean(buf.String()))
+ _, err := out.Write(b)
+ if err != nil {
+ errorOnLogging(err)
+ }
+ if l.printStack {
+ l.doPrintStack()
+ }
+
+ return linePrefix
+}
+
+func (l *logger) printf(out io.Writer, skipFrames int, severity string, err error, message string, args ...interface{}) string {
+ buf := bufferPool.Get()
+ defer bufferPool.Put(buf)
+
+ linePrefix := l.linePrefix(skipFrames)
+ buf.WriteString(severity)
+ buf.WriteString(" ")
+ buf.WriteString(linePrefix)
+ fmt.Fprintf(buf, message, args...)
+ printContext(buf, err)
+ buf.WriteByte('\n')
+ b := []byte(hidden.Clean(buf.String()))
+ _, err2 := out.Write(b)
+ if err2 != nil {
+ errorOnLogging(err)
+ }
+ if l.printStack {
+ l.doPrintStack()
+ }
+ return linePrefix
+}
+
+func (l *logger) Debug(arg interface{}) {
+ l.print(GetOutputs().DebugOut, 4, "DEBUG", arg)
+}
+
+func (l *logger) Debugf(message string, args ...interface{}) {
+ l.printf(GetOutputs().DebugOut, 4, "DEBUG", nil, message, args...)
+}
+
+func (l *logger) Error(arg interface{}) error {
+ return l.errorSkipFrames(arg, 1, ERROR)
+}
+
+func (l *logger) Errorf(message string, args ...interface{}) error {
+ return l.errorSkipFrames(errors.NewOffset(1, message, args...), 1, ERROR)
+}
+
+func (l *logger) Fatal(arg interface{}) {
+ fatal(l.errorSkipFrames(arg, 1, FATAL))
+}
+
+func (l *logger) Fatalf(message string, args ...interface{}) {
+ fatal(l.errorSkipFrames(errors.NewOffset(1, message, args...), 1, FATAL))
+}
+
+func fatal(err error) {
+ fn := onFatal.Load().(func(err error))
+ fn(err)
+}
+
+func (l *logger) errorSkipFrames(arg interface{}, skipFrames int, severity Severity) error {
+ var err error
+ switch e := arg.(type) {
+ case error:
+ err = e
+ default:
+ err = fmt.Errorf("%v", e)
+ }
+ linePrefix := l.print(GetOutputs().ErrorOut, skipFrames+4, severity.String(), err)
+ return report(err, linePrefix, severity)
+}
+
+func (l *logger) Trace(arg interface{}) {
+ if l.traceOn {
+ l.print(GetOutputs().DebugOut, 4, "TRACE", arg)
+ }
+}
+
+func (l *logger) Tracef(message string, args ...interface{}) {
+ if l.traceOn {
+ l.printf(GetOutputs().DebugOut, 4, "TRACE", nil, message, args...)
+ }
+}
+
+func (l *logger) TraceOut() io.Writer {
+ return l.traceOut
+}
+
+func (l *logger) IsTraceEnabled() bool {
+ return l.traceOn
+}
+
+func (l *logger) newTraceWriter() io.Writer {
+ pr, pw := io.Pipe()
+ br := bufio.NewReader(pr)
+
+ if !l.traceOn {
+ return pw
+ }
+ go func() {
+ defer func() {
+ if err := pr.Close(); err != nil {
+ errorOnLogging(err)
+ }
+ }()
+ defer func() {
+ if err := pw.Close(); err != nil {
+ errorOnLogging(err)
+ }
+ }()
+
+ for {
+ line, err := br.ReadString('\n')
+ if err == nil {
+ // Log the line (minus the trailing newline)
+ l.print(GetOutputs().DebugOut, 6, "TRACE", line[:len(line)-1])
+ } else {
+ l.printf(GetOutputs().DebugOut, 6, "TRACE", nil, "TraceWriter closed due to unexpected error: %v", err)
+ return
+ }
+ }
+ }()
+
+ return pw
+}
+
+type errorWriter struct {
+ l *logger
+}
+
+// Write implements method of io.Writer, due to different call depth,
+// it will not log correct file and line prefix
+func (w *errorWriter) Write(p []byte) (n int, err error) {
+ s := string(p)
+ if s[len(s)-1] == '\n' {
+ s = s[:len(s)-1]
+ }
+ w.l.print(GetOutputs().ErrorOut, 6, "ERROR", s)
+ return len(p), nil
+}
+
+func (l *logger) AsStdLogger() *log.Logger {
+ return log.New(&errorWriter{l}, "", 0)
+}
+
+func (l *logger) doPrintStack() {
+ var b []byte
+ buf := bytes.NewBuffer(b)
+ for _, pc := range l.pc {
+ funcForPc := runtime.FuncForPC(pc)
+ if funcForPc == nil {
+ break
+ }
+ name := funcForPc.Name()
+ if strings.HasPrefix(name, "runtime.") {
+ break
+ }
+ file, line := funcForPc.FileLine(pc)
+ fmt.Fprintf(buf, "\t%s\t%s: %d\n", name, file, line)
+ }
+ if _, err := buf.WriteTo(os.Stderr); err != nil {
+ errorOnLogging(err)
+ }
+}
+
+func errorOnLogging(err error) {
+ fmt.Fprintf(os.Stderr, "Unable to log: %v\n", err)
+}
+
+func printContext(buf *bytes.Buffer, err interface{}) {
+ // Note - we don't include globals when printing in order to avoid polluting the text log
+ values := ops.AsMap(err, false)
+ if len(values) == 0 {
+ return
+ }
+ buf.WriteString(" [")
+ var keys []string
+ for key := range values {
+ keys = append(keys, key)
+ }
+ sort.Strings(keys)
+ for i, key := range keys {
+ value := values[key]
+ if i > 0 {
+ buf.WriteString(" ")
+ }
+ buf.WriteString(key)
+ buf.WriteString("=")
+ fmt.Fprintf(buf, "%v", value)
+ }
+ buf.WriteByte(']')
+}
+
+func report(err error, linePrefix string, severity Severity) error {
+ var reportersCopy []ErrorReporter
+ reportersMutex.RLock()
+ if len(reporters) > 0 {
+ reportersCopy = make([]ErrorReporter, len(reporters))
+ copy(reportersCopy, reporters)
+ }
+ reportersMutex.RUnlock()
+
+ if len(reportersCopy) > 0 {
+ ctx := ops.AsMap(err, true)
+ ctx["severity"] = severity.String()
+ for _, reporter := range reportersCopy {
+ // We include globals when reporting
+ reporter(err, linePrefix, severity, ctx)
+ }
+ }
+ return err
+}
diff --git a/vendor/github.com/getlantern/golog/golog_test.go b/vendor/github.com/getlantern/golog/golog_test.go
new file mode 100644
index 0000000..bce36e8
--- /dev/null
+++ b/vendor/github.com/getlantern/golog/golog_test.go
@@ -0,0 +1,224 @@
+package golog
+
+import (
+ "bytes"
+ "io"
+ "io/ioutil"
+ "os"
+ "regexp"
+ "strings"
+ "sync"
+ "testing"
+ "time"
+
+ "github.com/getlantern/errors"
+ "github.com/getlantern/ops"
+
+ "github.com/stretchr/testify/assert"
+)
+
+var (
+ expectedLog = "SEVERITY myprefix: golog_test.go:999 Hello world\nSEVERITY myprefix: golog_test.go:999 Hello true [cvarA=a cvarB=b op=name root_op=name]\n"
+ expectedErrorLog = `ERROR myprefix: golog_test.go:999 Hello world [cvarC=c cvarD=d error=Hello %v error_location=github.com/getlantern/golog.TestError (golog_test.go:999) error_text=Hello world error_type=errors.Error op=name root_op=name]
+ERROR myprefix: golog_test.go:999 at github.com/getlantern/golog.TestError (golog_test.go:999)
+ERROR myprefix: golog_test.go:999 at testing.tRunner (testing.go:999)
+ERROR myprefix: golog_test.go:999 at runtime.goexit (asm_amd999.s:999)
+ERROR myprefix: golog_test.go:999 Caused by: world
+ERROR myprefix: golog_test.go:999 at github.com/getlantern/golog.errorReturner (golog_test.go:999)
+ERROR myprefix: golog_test.go:999 at github.com/getlantern/golog.TestError (golog_test.go:999)
+ERROR myprefix: golog_test.go:999 at testing.tRunner (testing.go:999)
+ERROR myprefix: golog_test.go:999 at runtime.goexit (asm_amd999.s:999)
+ERROR myprefix: golog_test.go:999 Hello true [cvarA=a cvarB=b cvarC=c error=%v %v error_location=github.com/getlantern/golog.TestError (golog_test.go:999) error_text=Hello true error_type=errors.Error op=name999 root_op=name999]
+ERROR myprefix: golog_test.go:999 at github.com/getlantern/golog.TestError (golog_test.go:999)
+ERROR myprefix: golog_test.go:999 at testing.tRunner (testing.go:999)
+ERROR myprefix: golog_test.go:999 at runtime.goexit (asm_amd999.s:999)
+ERROR myprefix: golog_test.go:999 Caused by: Hello
+ERROR myprefix: golog_test.go:999 at github.com/getlantern/golog.TestError (golog_test.go:999)
+ERROR myprefix: golog_test.go:999 at testing.tRunner (testing.go:999)
+ERROR myprefix: golog_test.go:999 at runtime.goexit (asm_amd999.s:999)
+`
+ expectedTraceLog = "TRACE myprefix: golog_test.go:999 Hello world\nTRACE myprefix: golog_test.go:999 Hello true\nTRACE myprefix: golog_test.go:999 Gravy\nTRACE myprefix: golog_test.go:999 TraceWriter closed due to unexpected error: EOF\n"
+ expectedStdLog = expectedLog
+)
+
+var (
+ replaceNumbers = regexp.MustCompile("[0-9]+")
+)
+
+func init() {
+ ops.SetGlobal("global", "shouldn't show up")
+}
+
+func expected(severity string, log string) string {
+ return strings.Replace(log, "SEVERITY", severity, -1)
+}
+
+func normalized(log string) string {
+ return replaceNumbers.ReplaceAllString(log, "999")
+}
+
+func TestReport(t *testing.T) {
+ SetOutputs(ioutil.Discard, ioutil.Discard)
+ OnFatal(func(err error) {
+ // ignore (prevents test from exiting)
+ })
+
+ errors := 0
+ fatals := 0
+ RegisterReporter(func(err error, linePrefix string, severity Severity, ctx map[string]interface{}) {
+ switch severity {
+ case ERROR:
+ errors++
+ case FATAL:
+ fatals++
+ }
+ })
+ l := LoggerFor("reporting")
+ l.Error("Some error")
+ l.Fatal("Fatal error")
+ assert.Equal(t, 1, errors)
+ assert.Equal(t, 1, fatals)
+}
+
+func TestDebug(t *testing.T) {
+ out := newBuffer()
+ SetOutputs(ioutil.Discard, out)
+ l := LoggerFor("myprefix")
+ l.Debug("Hello world")
+ defer ops.Begin("name").Set("cvarA", "a").Set("cvarB", "b").End()
+ l.Debugf("Hello %v", true)
+ assert.Equal(t, expected("DEBUG", expectedLog), out.String())
+}
+
+func TestError(t *testing.T) {
+ out := newBuffer()
+ SetOutputs(out, ioutil.Discard)
+ l := LoggerFor("myprefix")
+ ctx := ops.Begin("name").Set("cvarC", "c")
+ err := errorReturner()
+ err1 := errors.New("Hello %v", err)
+ err2 := errors.New("Hello")
+ ctx.End()
+ l.Error(err1)
+ defer ops.Begin("name2").Set("cvarA", "a").Set("cvarB", "b").End()
+ l.Errorf("%v %v", err2, true)
+ t.Log(out.String())
+ assert.Equal(t, expectedErrorLog, out.String())
+}
+
+func errorReturner() error {
+ defer ops.Begin("name").Set("cvarD", "d").End()
+ return errors.New("world")
+}
+
+func TestTraceEnabled(t *testing.T) {
+ originalTrace := os.Getenv("TRACE")
+ err := os.Setenv("TRACE", "true")
+ if err != nil {
+ t.Fatalf("Unable to set trace to true")
+ }
+ defer func() {
+ if err := os.Setenv("TRACE", originalTrace); err != nil {
+ t.Fatalf("Unable to set TRACE environment variable: %v", err)
+ }
+ }()
+
+ out := newBuffer()
+ SetOutputs(ioutil.Discard, out)
+ l := LoggerFor("myprefix")
+ l.Trace("Hello world")
+ l.Tracef("Hello %v", true)
+ tw := l.TraceOut()
+ if _, err := tw.Write([]byte("Gravy\n")); err != nil {
+ t.Fatalf("Unable to write: %v", err)
+ }
+ if err := tw.(io.Closer).Close(); err != nil {
+ t.Fatalf("Unable to close: %v", err)
+ }
+
+ // Give trace writer a moment to catch up
+ time.Sleep(50 * time.Millisecond)
+ assert.Regexp(t, expected("TRACE", expectedTraceLog), out.String())
+}
+
+func TestTraceDisabled(t *testing.T) {
+ originalTrace := os.Getenv("TRACE")
+ err := os.Setenv("TRACE", "false")
+ if err != nil {
+ t.Fatalf("Unable to set trace to false")
+ }
+ defer func() {
+ if err := os.Setenv("TRACE", originalTrace); err != nil {
+ t.Fatalf("Unable to set TRACE environment variable: %v", err)
+ }
+ }()
+
+ out := newBuffer()
+ SetOutputs(ioutil.Discard, out)
+ l := LoggerFor("myprefix")
+ l.Trace("Hello world")
+ l.Tracef("Hello %v", true)
+ if _, err := l.TraceOut().Write([]byte("Gravy\n")); err != nil {
+ t.Fatalf("Unable to write: %v", err)
+ }
+
+ // Give trace writer a moment to catch up
+ time.Sleep(50 * time.Millisecond)
+
+ assert.Equal(t, "", out.String(), "Nothing should have been logged")
+}
+
+func TestAsStdLogger(t *testing.T) {
+ out := newBuffer()
+ SetOutputs(out, ioutil.Discard)
+ l := LoggerFor("myprefix")
+ stdlog := l.AsStdLogger()
+ stdlog.Print("Hello world")
+ defer ops.Begin("name").Set("cvarA", "a").Set("cvarB", "b").End()
+ stdlog.Printf("Hello %v", true)
+ assert.Equal(t, expected("ERROR", expectedStdLog), out.String())
+}
+
+// TODO: TraceWriter appears to have been broken since we added line numbers
+// func TestTraceWriter(t *testing.T) {
+// originalTrace := os.Getenv("TRACE")
+// err := os.Setenv("TRACE", "true")
+// if err != nil {
+// t.Fatalf("Unable to set trace to true")
+// }
+// defer func() {
+// if err := os.Setenv("TRACE", originalTrace); err != nil {
+// t.Fatalf("Unable to set TRACE environment variable: %v", err)
+// }
+// }()
+//
+// out := newBuffer()
+// SetOutputs(ioutil.Discard, out)
+// l := LoggerFor("myprefix")
+// trace := l.TraceOut()
+// trace.Write([]byte("Hello world\n"))
+// defer ops.Begin().Set("cvarA", "a").Set("cvarB", "b").End()
+// trace.Write([]byte("Hello true\n"))
+// assert.Equal(t, expected("TRACE", expectedStdLog), out.String())
+// }
+
+func newBuffer() *synchronizedbuffer {
+ return &synchronizedbuffer{orig: &bytes.Buffer{}}
+}
+
+type synchronizedbuffer struct {
+ orig *bytes.Buffer
+ mutex sync.RWMutex
+}
+
+func (buf *synchronizedbuffer) Write(p []byte) (int, error) {
+ buf.mutex.Lock()
+ defer buf.mutex.Unlock()
+ return buf.orig.Write(p)
+}
+
+func (buf *synchronizedbuffer) String() string {
+ buf.mutex.RLock()
+ defer buf.mutex.RUnlock()
+ return normalized(buf.orig.String())
+}
diff --git a/vendor/github.com/getlantern/hex/hex.go b/vendor/github.com/getlantern/hex/hex.go
new file mode 100644
index 0000000..d1270e9
--- /dev/null
+++ b/vendor/github.com/getlantern/hex/hex.go
@@ -0,0 +1,107 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package hex implements hexadecimal encoding and decoding. It's taken almost
+// verbatim from golang/encoding/hex, however it allows using a different set
+// of encoding characters than the standard 0-F.
+package hex
+
+import (
+ "errors"
+ "fmt"
+)
+
+// DefaultEncoding behaves just like golang/encoding/hex.
+var DefaultEncoding = NewEncoding("0123456789abcdef")
+
+// An Encoding that uses a specific table of encoding characters.
+type Encoding struct {
+ hextable string
+}
+
+// NewEncoding constructs an Encoding using the given hextable.
+func NewEncoding(hextable string) *Encoding {
+ return &Encoding{hextable}
+}
+
+// EncodedLen returns the length of an encoding of n source bytes.
+func EncodedLen(n int) int { return n * 2 }
+
+// Encode encodes src into EncodedLen(len(src))
+// bytes of dst. As a convenience, it returns the number
+// of bytes written to dst, but this value is always EncodedLen(len(src)).
+// Encode implements hexadecimal encoding.
+func (e *Encoding) Encode(dst, src []byte) int {
+ for i, v := range src {
+ dst[i*2] = e.hextable[v>>4]
+ dst[i*2+1] = e.hextable[v&0x0f]
+ }
+
+ return len(src) * 2
+}
+
+// ErrLength results from decoding an odd length slice.
+var ErrLength = errors.New("encoding/hex: odd length hex string")
+
+// InvalidByteError values describe errors resulting from an invalid byte in a hex string.
+type InvalidByteError byte
+
+func (e InvalidByteError) Error() string {
+ return fmt.Sprintf("encoding/hex: invalid byte: %#U", rune(e))
+}
+
+func DecodedLen(x int) int { return x / 2 }
+
+// Decode decodes src into DecodedLen(len(src)) bytes, returning the actual
+// number of bytes written to dst.
+//
+// If Decode encounters invalid input, it returns an error describing the failure.
+func (e *Encoding) Decode(dst, src []byte) (int, error) {
+ if len(src)%2 == 1 {
+ return 0, ErrLength
+ }
+
+ for i := 0; i < len(src)/2; i++ {
+ a, ok := e.fromHexChar(src[i*2])
+ if !ok {
+ return 0, InvalidByteError(src[i*2])
+ }
+ b, ok := e.fromHexChar(src[i*2+1])
+ if !ok {
+ return 0, InvalidByteError(src[i*2+1])
+ }
+ dst[i] = (a << 4) | b
+ }
+
+ return len(src) / 2, nil
+}
+
+// fromHexChar converts a hex character into its value and a success flag.
+func (e *Encoding) fromHexChar(c byte) (byte, bool) {
+ for i, ch := range []byte(e.hextable) {
+ if ch == c {
+ return byte(i), true
+ }
+ }
+
+ return 0, false
+}
+
+// EncodeToString returns the hexadecimal encoding of src.
+func (e *Encoding) EncodeToString(src []byte) string {
+ dst := make([]byte, EncodedLen(len(src)))
+ e.Encode(dst, src)
+ return string(dst)
+}
+
+// DecodeString returns the bytes represented by the hexadecimal string s.
+func (e *Encoding) DecodeString(s string) ([]byte, error) {
+ src := []byte(s)
+ dst := make([]byte, DecodedLen(len(src)))
+ _, err := e.Decode(dst, src)
+ if err != nil {
+ return nil, err
+ }
+ return dst, nil
+}
diff --git a/vendor/github.com/getlantern/hidden/hidden.go b/vendor/github.com/getlantern/hidden/hidden.go
new file mode 100644
index 0000000..0c52b08
--- /dev/null
+++ b/vendor/github.com/getlantern/hidden/hidden.go
@@ -0,0 +1,66 @@
+// Package hidden provides the ability to "hide" binary data in a string using
+// a hex encoding with non-printing characters. Hidden data is demarcated with
+// a leading and trailing NUL character.
+package hidden
+
+import (
+ "bytes"
+ "fmt"
+ "regexp"
+
+ "github.com/getlantern/hex"
+)
+
+// 16 non-printing characters
+const hextable = "\x01\x02\x03\x04\x05\x06\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17"
+
+var (
+ hexencoding = hex.NewEncoding(hextable)
+
+ re *regexp.Regexp
+)
+
+func init() {
+ var err error
+ re, err = regexp.Compile(fmt.Sprintf("\x00[%v]+\x00", hextable))
+ if err != nil {
+ panic(err)
+ }
+}
+
+// ToString encodes the given data as a hidden string, including leadnig and
+// trailing NULs.
+func ToString(data []byte) string {
+ buf := bytes.NewBuffer(make([]byte, 0, 2+hex.EncodedLen(len(data))))
+ // Leading NUL
+ buf.WriteByte(0)
+ buf.WriteString(hexencoding.EncodeToString(data))
+ // Trailing NUL
+ buf.WriteByte(0)
+ return buf.String()
+}
+
+// FromString extracts the hidden data from a string, which is expected to
+// contain leading and trailing NULs.
+func FromString(str string) ([]byte, error) {
+ return hexencoding.DecodeString(str[1 : len(str)-1])
+}
+
+// Extract extracts all hidden data from an arbitrary string.
+func Extract(str string) ([][]byte, error) {
+ m := re.FindAllString(str, -1)
+ result := make([][]byte, 0, len(m))
+ for _, s := range m {
+ b, err := FromString(s)
+ if err != nil {
+ return nil, err
+ }
+ result = append(result, b)
+ }
+ return result, nil
+}
+
+// Clean removes any hidden data from an arbitrary string.
+func Clean(str string) string {
+ return re.ReplaceAllString(str, "")
+}
diff --git a/vendor/github.com/getlantern/hidden/hidden_test.go b/vendor/github.com/getlantern/hidden/hidden_test.go
new file mode 100644
index 0000000..420ce64
--- /dev/null
+++ b/vendor/github.com/getlantern/hidden/hidden_test.go
@@ -0,0 +1,33 @@
+package hidden
+
+import (
+ "encoding/binary"
+ "fmt"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestRoundTrip(t *testing.T) {
+ str := "H"
+ encoded := ToString([]byte(str))
+ rt, err := FromString(encoded)
+ if assert.NoError(t, err) {
+ assert.Equal(t, str, string(rt))
+ }
+}
+
+func TestExtract(t *testing.T) {
+ a := []byte("Here is my string")
+ b := make([]byte, 8)
+ binary.BigEndian.PutUint64(b, 56)
+ str := fmt.Sprintf("hidden%s data%s is fun", ToString(a), ToString(b))
+ t.Log(str)
+ out, err := Extract(str)
+ if assert.NoError(t, err) {
+ if assert.Len(t, out, 2) {
+ assert.Equal(t, out, [][]byte{a, b})
+ }
+ }
+ assert.Equal(t, "hidden data is fun", Clean(str))
+}
diff --git a/vendor/github.com/getlantern/ops/ops.go b/vendor/github.com/getlantern/ops/ops.go
new file mode 100644
index 0000000..136302d
--- /dev/null
+++ b/vendor/github.com/getlantern/ops/ops.go
@@ -0,0 +1,154 @@
+// Package ops provides a facility for tracking the processing of operations,
+// including contextual metadata about the operation and their final success or
+// failure. An op is assumed to have succeeded if by the time of calling Exit()
+// no errors have been reported. The final status can be reported to a metrics
+// facility.
+package ops
+
+import (
+ "sync"
+ "sync/atomic"
+
+ "github.com/getlantern/context"
+)
+
+var (
+ cm = context.NewManager()
+ reporters []Reporter
+ reportersMutex sync.RWMutex
+)
+
+// Reporter is a function that reports the success or failure of an Op. If
+// failure is nil, the Op can be considered successful.
+type Reporter func(failure error, ctx map[string]interface{})
+
+// Op represents an operation that's being performed. It mimics the API of
+// context.Context.
+type Op interface {
+ // Begin marks the beginning of an Op under this Op.
+ Begin(name string) Op
+
+ // Go starts the given function on a new goroutine.
+ Go(fn func())
+
+ // End marks the end of this op, at which point the Op will report its success
+ // or failure to all registered Reporters.
+ End()
+
+ // Cancel cancels this op so that even if End() is called later, it will not
+ // report its success or failure.
+ Cancel()
+
+ // Set puts a key->value pair into the current Op's context.
+ Set(key string, value interface{}) Op
+
+ // SetDynamic puts a key->value pair into the current Op's context, where the
+ // value is generated by a function that gets evaluated at every Read.
+ SetDynamic(key string, valueFN func() interface{}) Op
+
+ // FailIf marks this Op as failed if the given err is not nil. If FailIf is
+ // called multiple times, the latest error will be reported as the failure.
+ // Returns the original error for convenient chaining.
+ FailIf(err error) error
+}
+
+type op struct {
+ ctx context.Context
+ canceled bool
+ failure atomic.Value
+}
+
+// RegisterReporter registers the given reporter.
+func RegisterReporter(reporter Reporter) {
+ reportersMutex.Lock()
+ reporters = append(reporters, reporter)
+ reportersMutex.Unlock()
+}
+
+// Begin marks the beginning of a new Op.
+func Begin(name string) Op {
+ return &op{ctx: cm.Enter().Put("op", name).PutIfAbsent("root_op", name)}
+}
+
+func (o *op) Begin(name string) Op {
+ return &op{ctx: o.ctx.Enter().Put("op", name).PutIfAbsent("root_op", name)}
+}
+
+func (o *op) Go(fn func()) {
+ o.ctx.Go(fn)
+}
+
+// Go mimics the method from context.Manager.
+func Go(fn func()) {
+ cm.Go(fn)
+}
+
+func (o *op) Cancel() {
+ o.canceled = true
+}
+
+func (o *op) End() {
+ if o.canceled {
+ return
+ }
+
+ var reportersCopy []Reporter
+ reportersMutex.RLock()
+ if len(reporters) > 0 {
+ reportersCopy = make([]Reporter, len(reporters))
+ copy(reportersCopy, reporters)
+ }
+ reportersMutex.RUnlock()
+
+ if len(reportersCopy) > 0 {
+ var failure error
+ _failure := o.failure.Load()
+ ctx := o.ctx.AsMap(_failure, true)
+ if _failure != nil {
+ failure = _failure.(error)
+ _, errorSet := ctx["error"]
+ if !errorSet {
+ ctx["error"] = failure.Error()
+ }
+ }
+ for _, reporter := range reportersCopy {
+ reporter(failure, ctx)
+ }
+ }
+
+ o.ctx.Exit()
+}
+
+func (o *op) Set(key string, value interface{}) Op {
+ o.ctx.Put(key, value)
+ return o
+}
+
+// SetGlobal puts a key->value pair into the global context, which is inherited
+// by all Ops.
+func SetGlobal(key string, value interface{}) {
+ cm.PutGlobal(key, value)
+}
+
+func (o *op) SetDynamic(key string, valueFN func() interface{}) Op {
+ o.ctx.PutDynamic(key, valueFN)
+ return o
+}
+
+// SetGlobalDynamic is like SetGlobal but uses a function to derive the value
+// at read time.
+func SetGlobalDynamic(key string, valueFN func() interface{}) {
+ cm.PutGlobalDynamic(key, valueFN)
+}
+
+// AsMap mimics the method from context.Manager.
+func AsMap(obj interface{}, includeGlobals bool) context.Map {
+ return cm.AsMap(obj, includeGlobals)
+}
+
+func (o *op) FailIf(err error) error {
+ if err != nil {
+ o.failure.Store(err)
+ }
+ return err
+}
diff --git a/vendor/github.com/getlantern/ops/ops_test.go b/vendor/github.com/getlantern/ops/ops_test.go
new file mode 100644
index 0000000..1c16e73
--- /dev/null
+++ b/vendor/github.com/getlantern/ops/ops_test.go
@@ -0,0 +1,76 @@
+package ops_test
+
+import (
+ "sync"
+ "testing"
+
+ "github.com/getlantern/errors"
+ "github.com/getlantern/ops"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestSuccess(t *testing.T) {
+ var reportedFailure error
+ var reportedCtx map[string]interface{}
+ report := func(failure error, ctx map[string]interface{}) {
+ reportedFailure = failure
+ reportedCtx = ctx
+ }
+
+ ops.RegisterReporter(report)
+ ops.SetGlobal("g", "g1")
+ op := ops.Begin("test_success").Set("a", 1).SetDynamic("b", func() interface{} { return 2 })
+ defer op.End()
+ innerOp := op.Begin("inside")
+ innerOp.FailIf(nil)
+ innerOp.End()
+
+ assert.Nil(t, reportedFailure)
+ expectedCtx := map[string]interface{}{
+ "op": "inside",
+ "root_op": "test_success",
+ "g": "g1",
+ "a": 1,
+ "b": 2,
+ }
+ assert.Equal(t, expectedCtx, reportedCtx)
+}
+
+func TestFailure(t *testing.T) {
+ doTestFailure(t, false)
+}
+
+func TestCancel(t *testing.T) {
+ doTestFailure(t, true)
+}
+
+func doTestFailure(t *testing.T, cancel bool) {
+ var reportedFailure error
+ var reportedCtx map[string]interface{}
+ report := func(failure error, ctx map[string]interface{}) {
+ reportedFailure = failure
+ reportedCtx = ctx
+ }
+
+ ops.RegisterReporter(report)
+ op := ops.Begin("test_failure")
+ var wg sync.WaitGroup
+ wg.Add(1)
+ op.Go(func() {
+ op.FailIf(errors.New("I failed").With("errorcontext", 5))
+ wg.Done()
+ })
+ wg.Wait()
+ if cancel {
+ op.Cancel()
+ }
+ op.End()
+
+ if cancel {
+ assert.Nil(t, reportedFailure)
+ assert.Nil(t, reportedCtx)
+ } else {
+ assert.Contains(t, reportedFailure.Error(), "I failed")
+ assert.Equal(t, 5, reportedCtx["errorcontext"])
+ }
+}