~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/go4/lock/lock_test.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
Copyright 2013 The Go Authors
 
3
 
 
4
Licensed under the Apache License, Version 2.0 (the "License");
 
5
you may not use this file except in compliance with the License.
 
6
You may obtain a copy of the License at
 
7
 
 
8
     http://www.apache.org/licenses/LICENSE-2.0
 
9
 
 
10
Unless required by applicable law or agreed to in writing, software
 
11
distributed under the License is distributed on an "AS IS" BASIS,
 
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
13
See the License for the specific language governing permissions and
 
14
limitations under the License.
 
15
*/
 
16
 
 
17
package lock
 
18
 
 
19
import (
 
20
        "bufio"
 
21
        "errors"
 
22
        "fmt"
 
23
        "io"
 
24
        "io/ioutil"
 
25
        "os"
 
26
        "os/exec"
 
27
        "path/filepath"
 
28
        "strconv"
 
29
        "testing"
 
30
)
 
31
 
 
32
func TestLock(t *testing.T) {
 
33
        testLock(t, false)
 
34
}
 
35
 
 
36
func TestLockPortable(t *testing.T) {
 
37
        testLock(t, true)
 
38
}
 
39
 
 
40
func TestLockInChild(t *testing.T) {
 
41
        f := os.Getenv("TEST_LOCK_FILE")
 
42
        if f == "" {
 
43
                // not child
 
44
                return
 
45
        }
 
46
        lock := Lock
 
47
        if v, _ := strconv.ParseBool(os.Getenv("TEST_LOCK_PORTABLE")); v {
 
48
                lock = lockPortable
 
49
        }
 
50
 
 
51
        var lk io.Closer
 
52
        for scan := bufio.NewScanner(os.Stdin); scan.Scan(); {
 
53
                var err error
 
54
                switch scan.Text() {
 
55
                case "lock":
 
56
                        lk, err = lock(f)
 
57
                case "unlock":
 
58
                        err = lk.Close()
 
59
                        lk = nil
 
60
                case "exit":
 
61
                        // Simulate a crash, or at least not unlocking the lock.
 
62
                        os.Exit(0)
 
63
                default:
 
64
                        err = fmt.Errorf("unexpected child command %q", scan.Text())
 
65
                }
 
66
                if err != nil {
 
67
                        fmt.Println(err)
 
68
                } else {
 
69
                        fmt.Println("")
 
70
                }
 
71
        }
 
72
}
 
73
 
 
74
func testLock(t *testing.T, portable bool) {
 
75
        lock := Lock
 
76
        if portable {
 
77
                lock = lockPortable
 
78
        }
 
79
        t.Logf("test lock, portable %v", portable)
 
80
 
 
81
        td, err := ioutil.TempDir("", "")
 
82
        if err != nil {
 
83
                t.Fatal(err)
 
84
        }
 
85
        defer os.RemoveAll(td)
 
86
 
 
87
        path := filepath.Join(td, "foo.lock")
 
88
 
 
89
        proc := newChildProc(t, path, portable)
 
90
        defer proc.kill()
 
91
 
 
92
        t.Logf("First lock in child")
 
93
        if err := proc.do("lock"); err != nil {
 
94
                t.Fatalf("first lock in child process: %v", err)
 
95
        }
 
96
 
 
97
        t.Logf("Crash child")
 
98
        if err := proc.do("exit"); err != nil {
 
99
                t.Fatalf("crash in child process: %v", err)
 
100
        }
 
101
 
 
102
        proc = newChildProc(t, path, portable)
 
103
        defer proc.kill()
 
104
 
 
105
        t.Logf("Locking+unlocking in child...")
 
106
        if err := proc.do("lock"); err != nil {
 
107
                t.Fatalf("lock in child process after crashing child: %v", err)
 
108
        }
 
109
        if err := proc.do("unlock"); err != nil {
 
110
                t.Fatalf("lock in child process after crashing child: %v", err)
 
111
        }
 
112
 
 
113
        t.Logf("Locking in parent...")
 
114
        lk1, err := lock(path)
 
115
        if err != nil {
 
116
                t.Fatal(err)
 
117
        }
 
118
 
 
119
        t.Logf("Again in parent...")
 
120
        _, err = lock(path)
 
121
        if err == nil {
 
122
                t.Fatal("expected second lock to fail")
 
123
        }
 
124
 
 
125
        t.Logf("Locking in child...")
 
126
        if err := proc.do("lock"); err == nil {
 
127
                t.Fatalf("expected lock in child process to fail")
 
128
        }
 
129
 
 
130
        t.Logf("Unlocking lock in parent")
 
131
        if err := lk1.Close(); err != nil {
 
132
                t.Fatal(err)
 
133
        }
 
134
 
 
135
        t.Logf("Trying lock again in child...")
 
136
        if err := proc.do("lock"); err != nil {
 
137
                t.Fatal(err)
 
138
        }
 
139
        if err := proc.do("unlock"); err != nil {
 
140
                t.Fatal(err)
 
141
        }
 
142
 
 
143
        lk3, err := lock(path)
 
144
        if err != nil {
 
145
                t.Fatal(err)
 
146
        }
 
147
        lk3.Close()
 
148
}
 
149
 
 
150
type childLockCmd struct {
 
151
        op    string
 
152
        reply chan<- error
 
153
}
 
154
 
 
155
type childProc struct {
 
156
        proc *os.Process
 
157
        c    chan childLockCmd
 
158
}
 
159
 
 
160
func (c *childProc) kill() {
 
161
        c.proc.Kill()
 
162
}
 
163
 
 
164
func (c *childProc) do(op string) error {
 
165
        reply := make(chan error)
 
166
        c.c <- childLockCmd{
 
167
                op:    op,
 
168
                reply: reply,
 
169
        }
 
170
        return <-reply
 
171
}
 
172
 
 
173
func newChildProc(t *testing.T, path string, portable bool) *childProc {
 
174
        cmd := exec.Command(os.Args[0], "-test.run=LockInChild$")
 
175
        cmd.Env = []string{"TEST_LOCK_FILE=" + path}
 
176
        toChild, err := cmd.StdinPipe()
 
177
        if err != nil {
 
178
                t.Fatalf("cannot make pipe: %v", err)
 
179
        }
 
180
        fromChild, err := cmd.StdoutPipe()
 
181
        if err != nil {
 
182
                t.Fatalf("cannot make pipe: %v", err)
 
183
        }
 
184
        cmd.Stderr = os.Stderr
 
185
        if portable {
 
186
                cmd.Env = append(cmd.Env, "TEST_LOCK_PORTABLE=1")
 
187
        }
 
188
        if err := cmd.Start(); err != nil {
 
189
                t.Fatalf("cannot start child: %v", err)
 
190
        }
 
191
        cmdChan := make(chan childLockCmd)
 
192
        go func() {
 
193
                defer fromChild.Close()
 
194
                defer toChild.Close()
 
195
                inScan := bufio.NewScanner(fromChild)
 
196
                for c := range cmdChan {
 
197
                        fmt.Fprintln(toChild, c.op)
 
198
                        ok := inScan.Scan()
 
199
                        if c.op == "exit" {
 
200
                                if ok {
 
201
                                        c.reply <- errors.New("child did not exit")
 
202
                                } else {
 
203
                                        cmd.Wait()
 
204
                                        c.reply <- nil
 
205
                                }
 
206
                                break
 
207
                        }
 
208
                        if !ok {
 
209
                                panic("child exited early")
 
210
                        }
 
211
                        if errText := inScan.Text(); errText != "" {
 
212
                                c.reply <- errors.New(errText)
 
213
                        } else {
 
214
                                c.reply <- nil
 
215
                        }
 
216
                }
 
217
        }()
 
218
        return &childProc{
 
219
                c:    cmdChan,
 
220
                proc: cmd.Process,
 
221
        }
 
222
}