14716: Removes test config loading func that short-circuited load mechanism.
[arvados.git] / services / keep-web / server_test.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package main
6
7 import (
8         "bytes"
9         "crypto/md5"
10         "encoding/json"
11         "fmt"
12         "io"
13         "io/ioutil"
14         "net"
15         "net/http"
16         "os"
17         "os/exec"
18         "strings"
19         "testing"
20         "time"
21
22         "git.curoverse.com/arvados.git/lib/config"
23         "git.curoverse.com/arvados.git/sdk/go/arvados"
24         "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
25         "git.curoverse.com/arvados.git/sdk/go/arvadostest"
26         "git.curoverse.com/arvados.git/sdk/go/keepclient"
27         log "github.com/sirupsen/logrus"
28         check "gopkg.in/check.v1"
29 )
30
31 var testAPIHost = os.Getenv("ARVADOS_API_HOST")
32
33 var _ = check.Suite(&IntegrationSuite{})
34
35 // IntegrationSuite tests need an API server and a keep-web server
36 type IntegrationSuite struct {
37         testServer *server
38 }
39
40 func (s *IntegrationSuite) TestNoToken(c *check.C) {
41         for _, token := range []string{
42                 "",
43                 "bogustoken",
44         } {
45                 hdr, body, _ := s.runCurl(c, token, "collections.example.com", "/collections/"+arvadostest.FooCollection+"/foo")
46                 c.Check(hdr, check.Matches, `(?s)HTTP/1.1 404 Not Found\r\n.*`)
47                 c.Check(body, check.Equals, "")
48
49                 if token != "" {
50                         hdr, body, _ = s.runCurl(c, token, "collections.example.com", "/collections/download/"+arvadostest.FooCollection+"/"+token+"/foo")
51                         c.Check(hdr, check.Matches, `(?s)HTTP/1.1 404 Not Found\r\n.*`)
52                         c.Check(body, check.Equals, "")
53                 }
54
55                 hdr, body, _ = s.runCurl(c, token, "collections.example.com", "/bad-route")
56                 c.Check(hdr, check.Matches, `(?s)HTTP/1.1 404 Not Found\r\n.*`)
57                 c.Check(body, check.Equals, "")
58         }
59 }
60
61 // TODO: Move most cases to functional tests -- at least use Go's own
62 // http client instead of forking curl. Just leave enough of an
63 // integration test to assure that the documented way of invoking curl
64 // really works against the server.
65 func (s *IntegrationSuite) Test404(c *check.C) {
66         for _, uri := range []string{
67                 // Routing errors (always 404 regardless of what's stored in Keep)
68                 "/foo",
69                 "/download",
70                 "/collections",
71                 "/collections/",
72                 // Implicit/generated index is not implemented yet;
73                 // until then, return 404.
74                 "/collections/" + arvadostest.FooCollection,
75                 "/collections/" + arvadostest.FooCollection + "/",
76                 "/collections/" + arvadostest.FooBarDirCollection + "/dir1",
77                 "/collections/" + arvadostest.FooBarDirCollection + "/dir1/",
78                 // Non-existent file in collection
79                 "/collections/" + arvadostest.FooCollection + "/theperthcountyconspiracy",
80                 "/collections/download/" + arvadostest.FooCollection + "/" + arvadostest.ActiveToken + "/theperthcountyconspiracy",
81                 // Non-existent collection
82                 "/collections/" + arvadostest.NonexistentCollection,
83                 "/collections/" + arvadostest.NonexistentCollection + "/",
84                 "/collections/" + arvadostest.NonexistentCollection + "/theperthcountyconspiracy",
85                 "/collections/download/" + arvadostest.NonexistentCollection + "/" + arvadostest.ActiveToken + "/theperthcountyconspiracy",
86         } {
87                 hdr, body, _ := s.runCurl(c, arvadostest.ActiveToken, "collections.example.com", uri)
88                 c.Check(hdr, check.Matches, "(?s)HTTP/1.1 404 Not Found\r\n.*")
89                 if len(body) > 0 {
90                         c.Check(body, check.Equals, "404 page not found\n")
91                 }
92         }
93 }
94
95 func (s *IntegrationSuite) Test1GBFile(c *check.C) {
96         if testing.Short() {
97                 c.Skip("skipping 1GB integration test in short mode")
98         }
99         s.test100BlockFile(c, 10000000)
100 }
101
102 func (s *IntegrationSuite) Test100BlockFile(c *check.C) {
103         if testing.Short() {
104                 // 3 MB
105                 s.test100BlockFile(c, 30000)
106         } else {
107                 // 300 MB
108                 s.test100BlockFile(c, 3000000)
109         }
110 }
111
112 func (s *IntegrationSuite) test100BlockFile(c *check.C, blocksize int) {
113         testdata := make([]byte, blocksize)
114         for i := 0; i < blocksize; i++ {
115                 testdata[i] = byte(' ')
116         }
117         arv, err := arvadosclient.MakeArvadosClient()
118         c.Assert(err, check.Equals, nil)
119         arv.ApiToken = arvadostest.ActiveToken
120         kc, err := keepclient.MakeKeepClient(arv)
121         c.Assert(err, check.Equals, nil)
122         loc, _, err := kc.PutB(testdata[:])
123         c.Assert(err, check.Equals, nil)
124         mtext := "."
125         for i := 0; i < 100; i++ {
126                 mtext = mtext + " " + loc
127         }
128         mtext = mtext + fmt.Sprintf(" 0:%d00:testdata.bin\n", blocksize)
129         coll := map[string]interface{}{}
130         err = arv.Create("collections",
131                 map[string]interface{}{
132                         "collection": map[string]interface{}{
133                                 "name":          fmt.Sprintf("testdata blocksize=%d", blocksize),
134                                 "manifest_text": mtext,
135                         },
136                 }, &coll)
137         c.Assert(err, check.Equals, nil)
138         uuid := coll["uuid"].(string)
139
140         hdr, body, size := s.runCurl(c, arv.ApiToken, uuid+".collections.example.com", "/testdata.bin")
141         c.Check(hdr, check.Matches, `(?s)HTTP/1.1 200 OK\r\n.*`)
142         c.Check(hdr, check.Matches, `(?si).*Content-length: `+fmt.Sprintf("%d00", blocksize)+`\r\n.*`)
143         c.Check([]byte(body)[:1234], check.DeepEquals, testdata[:1234])
144         c.Check(size, check.Equals, int64(blocksize)*100)
145 }
146
147 type curlCase struct {
148         auth    string
149         host    string
150         path    string
151         dataMD5 string
152 }
153
154 func (s *IntegrationSuite) Test200(c *check.C) {
155         s.testServer.Config.cluster.Users.AnonymousUserToken = arvadostest.AnonymousToken
156         for _, spec := range []curlCase{
157                 // My collection
158                 {
159                         auth:    arvadostest.ActiveToken,
160                         host:    arvadostest.FooCollection + "--collections.example.com",
161                         path:    "/foo",
162                         dataMD5: "acbd18db4cc2f85cedef654fccc4a4d8",
163                 },
164                 {
165                         auth:    arvadostest.ActiveToken,
166                         host:    arvadostest.FooCollection + ".collections.example.com",
167                         path:    "/foo",
168                         dataMD5: "acbd18db4cc2f85cedef654fccc4a4d8",
169                 },
170                 {
171                         host:    strings.Replace(arvadostest.FooCollectionPDH, "+", "-", 1) + ".collections.example.com",
172                         path:    "/t=" + arvadostest.ActiveToken + "/foo",
173                         dataMD5: "acbd18db4cc2f85cedef654fccc4a4d8",
174                 },
175                 {
176                         path:    "/c=" + arvadostest.FooCollectionPDH + "/t=" + arvadostest.ActiveToken + "/foo",
177                         dataMD5: "acbd18db4cc2f85cedef654fccc4a4d8",
178                 },
179                 {
180                         path:    "/c=" + strings.Replace(arvadostest.FooCollectionPDH, "+", "-", 1) + "/t=" + arvadostest.ActiveToken + "/_/foo",
181                         dataMD5: "acbd18db4cc2f85cedef654fccc4a4d8",
182                 },
183                 {
184                         path:    "/collections/download/" + arvadostest.FooCollection + "/" + arvadostest.ActiveToken + "/foo",
185                         dataMD5: "acbd18db4cc2f85cedef654fccc4a4d8",
186                 },
187                 {
188                         auth:    "tokensobogus",
189                         path:    "/collections/download/" + arvadostest.FooCollection + "/" + arvadostest.ActiveToken + "/foo",
190                         dataMD5: "acbd18db4cc2f85cedef654fccc4a4d8",
191                 },
192                 {
193                         auth:    arvadostest.ActiveToken,
194                         path:    "/collections/download/" + arvadostest.FooCollection + "/" + arvadostest.ActiveToken + "/foo",
195                         dataMD5: "acbd18db4cc2f85cedef654fccc4a4d8",
196                 },
197                 {
198                         auth:    arvadostest.AnonymousToken,
199                         path:    "/collections/download/" + arvadostest.FooCollection + "/" + arvadostest.ActiveToken + "/foo",
200                         dataMD5: "acbd18db4cc2f85cedef654fccc4a4d8",
201                 },
202
203                 // Anonymously accessible data
204                 {
205                         path:    "/c=" + arvadostest.HelloWorldCollection + "/Hello%20world.txt",
206                         dataMD5: "f0ef7081e1539ac00ef5b761b4fb01b3",
207                 },
208                 {
209                         host:    arvadostest.HelloWorldCollection + ".collections.example.com",
210                         path:    "/Hello%20world.txt",
211                         dataMD5: "f0ef7081e1539ac00ef5b761b4fb01b3",
212                 },
213                 {
214                         host:    arvadostest.HelloWorldCollection + ".collections.example.com",
215                         path:    "/_/Hello%20world.txt",
216                         dataMD5: "f0ef7081e1539ac00ef5b761b4fb01b3",
217                 },
218                 {
219                         path:    "/collections/" + arvadostest.HelloWorldCollection + "/Hello%20world.txt",
220                         dataMD5: "f0ef7081e1539ac00ef5b761b4fb01b3",
221                 },
222                 {
223                         auth:    arvadostest.ActiveToken,
224                         path:    "/collections/" + arvadostest.HelloWorldCollection + "/Hello%20world.txt",
225                         dataMD5: "f0ef7081e1539ac00ef5b761b4fb01b3",
226                 },
227                 {
228                         auth:    arvadostest.SpectatorToken,
229                         path:    "/collections/" + arvadostest.HelloWorldCollection + "/Hello%20world.txt",
230                         dataMD5: "f0ef7081e1539ac00ef5b761b4fb01b3",
231                 },
232                 {
233                         auth:    arvadostest.SpectatorToken,
234                         host:    arvadostest.HelloWorldCollection + "--collections.example.com",
235                         path:    "/Hello%20world.txt",
236                         dataMD5: "f0ef7081e1539ac00ef5b761b4fb01b3",
237                 },
238                 {
239                         auth:    arvadostest.SpectatorToken,
240                         path:    "/collections/download/" + arvadostest.HelloWorldCollection + "/" + arvadostest.SpectatorToken + "/Hello%20world.txt",
241                         dataMD5: "f0ef7081e1539ac00ef5b761b4fb01b3",
242                 },
243         } {
244                 host := spec.host
245                 if host == "" {
246                         host = "collections.example.com"
247                 }
248                 hdr, body, _ := s.runCurl(c, spec.auth, host, spec.path)
249                 c.Check(hdr, check.Matches, `(?s)HTTP/1.1 200 OK\r\n.*`)
250                 if strings.HasSuffix(spec.path, ".txt") {
251                         c.Check(hdr, check.Matches, `(?s).*\r\nContent-Type: text/plain.*`)
252                         // TODO: Check some types that aren't
253                         // automatically detected by Go's http server
254                         // by sniffing the content.
255                 }
256                 c.Check(fmt.Sprintf("%x", md5.Sum([]byte(body))), check.Equals, spec.dataMD5)
257         }
258 }
259
260 // Return header block and body.
261 func (s *IntegrationSuite) runCurl(c *check.C, token, host, uri string, args ...string) (hdr, bodyPart string, bodySize int64) {
262         curlArgs := []string{"--silent", "--show-error", "--include"}
263         testHost, testPort, _ := net.SplitHostPort(s.testServer.Addr)
264         curlArgs = append(curlArgs, "--resolve", host+":"+testPort+":"+testHost)
265         if token != "" {
266                 curlArgs = append(curlArgs, "-H", "Authorization: OAuth2 "+token)
267         }
268         curlArgs = append(curlArgs, args...)
269         curlArgs = append(curlArgs, "http://"+host+":"+testPort+uri)
270         c.Log(fmt.Sprintf("curlArgs == %#v", curlArgs))
271         cmd := exec.Command("curl", curlArgs...)
272         stdout, err := cmd.StdoutPipe()
273         c.Assert(err, check.IsNil)
274         cmd.Stderr = os.Stderr
275         err = cmd.Start()
276         c.Assert(err, check.IsNil)
277         buf := make([]byte, 2<<27)
278         n, err := io.ReadFull(stdout, buf)
279         // Discard (but measure size of) anything past 128 MiB.
280         var discarded int64
281         if err == io.ErrUnexpectedEOF {
282                 buf = buf[:n]
283         } else {
284                 c.Assert(err, check.IsNil)
285                 discarded, err = io.Copy(ioutil.Discard, stdout)
286                 c.Assert(err, check.IsNil)
287         }
288         err = cmd.Wait()
289         // Without "-f", curl exits 0 as long as it gets a valid HTTP
290         // response from the server, even if the response status
291         // indicates that the request failed. In our test suite, we
292         // always expect a valid HTTP response, and we parse the
293         // headers ourselves. If curl exits non-zero, our testing
294         // environment is broken.
295         c.Assert(err, check.Equals, nil)
296         hdrsAndBody := strings.SplitN(string(buf), "\r\n\r\n", 2)
297         c.Assert(len(hdrsAndBody), check.Equals, 2)
298         hdr = hdrsAndBody[0]
299         bodyPart = hdrsAndBody[1]
300         bodySize = int64(len(bodyPart)) + discarded
301         return
302 }
303
304 func (s *IntegrationSuite) TestMetrics(c *check.C) {
305         s.testServer.Config.cluster.Services.WebDAVDownload.ExternalURL.Host = s.testServer.Addr
306         origin := "http://" + s.testServer.Addr
307         req, _ := http.NewRequest("GET", origin+"/notfound", nil)
308         _, err := http.DefaultClient.Do(req)
309         c.Assert(err, check.IsNil)
310         req, _ = http.NewRequest("GET", origin+"/by_id/", nil)
311         req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
312         resp, err := http.DefaultClient.Do(req)
313         c.Assert(err, check.IsNil)
314         c.Check(resp.StatusCode, check.Equals, http.StatusOK)
315         for i := 0; i < 2; i++ {
316                 req, _ = http.NewRequest("GET", origin+"/foo", nil)
317                 req.Host = arvadostest.FooCollection + ".example.com"
318                 req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
319                 resp, err = http.DefaultClient.Do(req)
320                 c.Assert(err, check.IsNil)
321                 c.Check(resp.StatusCode, check.Equals, http.StatusOK)
322                 buf, _ := ioutil.ReadAll(resp.Body)
323                 c.Check(buf, check.DeepEquals, []byte("foo"))
324                 resp.Body.Close()
325         }
326
327         s.testServer.Config.Cache.updateGauges()
328
329         req, _ = http.NewRequest("GET", origin+"/metrics.json", nil)
330         resp, err = http.DefaultClient.Do(req)
331         c.Assert(err, check.IsNil)
332         c.Check(resp.StatusCode, check.Equals, http.StatusUnauthorized)
333
334         req, _ = http.NewRequest("GET", origin+"/metrics.json", nil)
335         req.Header.Set("Authorization", "Bearer badtoken")
336         resp, err = http.DefaultClient.Do(req)
337         c.Assert(err, check.IsNil)
338         c.Check(resp.StatusCode, check.Equals, http.StatusForbidden)
339
340         req, _ = http.NewRequest("GET", origin+"/metrics.json", nil)
341         req.Header.Set("Authorization", "Bearer "+arvadostest.ManagementToken)
342         resp, err = http.DefaultClient.Do(req)
343         c.Assert(err, check.IsNil)
344         c.Check(resp.StatusCode, check.Equals, http.StatusOK)
345         type summary struct {
346                 SampleCount string  `json:"sample_count"`
347                 SampleSum   float64 `json:"sample_sum"`
348                 Quantile    []struct {
349                         Quantile float64
350                         Value    float64
351                 }
352         }
353         type counter struct {
354                 Value int64
355         }
356         type gauge struct {
357                 Value float64
358         }
359         var ents []struct {
360                 Name   string
361                 Help   string
362                 Type   string
363                 Metric []struct {
364                         Label []struct {
365                                 Name  string
366                                 Value string
367                         }
368                         Counter counter
369                         Gauge   gauge
370                         Summary summary
371                 }
372         }
373         json.NewDecoder(resp.Body).Decode(&ents)
374         summaries := map[string]summary{}
375         gauges := map[string]gauge{}
376         counters := map[string]counter{}
377         for _, e := range ents {
378                 for _, m := range e.Metric {
379                         labels := map[string]string{}
380                         for _, lbl := range m.Label {
381                                 labels[lbl.Name] = lbl.Value
382                         }
383                         summaries[e.Name+"/"+labels["method"]+"/"+labels["code"]] = m.Summary
384                         counters[e.Name+"/"+labels["method"]+"/"+labels["code"]] = m.Counter
385                         gauges[e.Name+"/"+labels["method"]+"/"+labels["code"]] = m.Gauge
386                 }
387         }
388         c.Check(summaries["request_duration_seconds/get/200"].SampleSum, check.Not(check.Equals), 0)
389         c.Check(summaries["request_duration_seconds/get/200"].SampleCount, check.Equals, "3")
390         c.Check(summaries["request_duration_seconds/get/404"].SampleCount, check.Equals, "1")
391         c.Check(summaries["time_to_status_seconds/get/404"].SampleCount, check.Equals, "1")
392         c.Check(counters["arvados_keepweb_collectioncache_requests//"].Value, check.Equals, int64(2))
393         c.Check(counters["arvados_keepweb_collectioncache_api_calls//"].Value, check.Equals, int64(1))
394         c.Check(counters["arvados_keepweb_collectioncache_hits//"].Value, check.Equals, int64(1))
395         c.Check(counters["arvados_keepweb_collectioncache_pdh_hits//"].Value, check.Equals, int64(1))
396         c.Check(counters["arvados_keepweb_collectioncache_permission_hits//"].Value, check.Equals, int64(1))
397         c.Check(gauges["arvados_keepweb_collectioncache_cached_manifests//"].Value, check.Equals, float64(1))
398         // FooCollection's cached manifest size is 45 ("1f4b0....+45") plus one 51-byte blob signature
399         c.Check(gauges["arvados_keepweb_collectioncache_cached_manifest_bytes//"].Value, check.Equals, float64(45+51))
400
401         // If the Host header indicates a collection, /metrics.json
402         // refers to a file in the collection -- the metrics handler
403         // must not intercept that route.
404         req, _ = http.NewRequest("GET", origin+"/metrics.json", nil)
405         req.Host = strings.Replace(arvadostest.FooCollectionPDH, "+", "-", -1) + ".example.com"
406         req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
407         resp, err = http.DefaultClient.Do(req)
408         c.Assert(err, check.IsNil)
409         c.Check(resp.StatusCode, check.Equals, http.StatusNotFound)
410 }
411
412 func (s *UnitSuite) TestLegacyConfig(c *check.C) {
413         content := []byte(`
414 {
415         "Client": {
416                 "Scheme": "",
417                 "APIHost": "example.com",
418                 "AuthToken": "abcdefg",
419         },
420         "Listen": ":80",
421         "AnonymousTokens": [
422                 "anonusertoken"
423         ],
424         "AttachmentOnlyHost": "download.example.com",
425         "TrustAllContent": true,
426         "Cache": {
427                 "TTL": "1m",
428                 "UUIDTTL": "1s",
429                 "MaxCollectionEntries": 42,
430                 "MaxCollectionBytes": 1234567890,
431                 "MaxPermissionEntries": 100,
432                 "MaxUUIDEntries": 100
433         },
434         "ManagementToken": "xyzzy"
435 }
436 `)
437         tmpfile, err := ioutil.TempFile("", "example")
438         if err != nil {
439                 c.Error(err)
440         }
441         defer os.Remove(tmpfile.Name())
442
443         if _, err := tmpfile.Write(content); err != nil {
444                 c.Error(err)
445         }
446         if err := tmpfile.Close(); err != nil {
447                 c.Error(err)
448         }
449         cfg := configure(log.New(), []string{"keep-web", "-config", tmpfile.Name()})
450         c.Check(cfg, check.NotNil)
451         c.Check(cfg.cluster, check.NotNil)
452
453         c.Check(cfg.cluster.Services.Controller.ExternalURL, check.Equals, arvados.URL{Scheme: "https", Host: "example.com"})
454         c.Check(cfg.cluster.SystemRootToken, check.Equals, "abcdefg")
455
456         c.Check(cfg.cluster.Collections.WebDAVCache.TTL, check.Equals, arvados.Duration(60*time.Second))
457         c.Check(cfg.cluster.Collections.WebDAVCache.UUIDTTL, check.Equals, arvados.Duration(time.Second))
458         c.Check(cfg.cluster.Collections.WebDAVCache.MaxCollectionEntries, check.Equals, 42)
459         c.Check(cfg.cluster.Collections.WebDAVCache.MaxCollectionBytes, check.Equals, int64(1234567890))
460         c.Check(cfg.cluster.Collections.WebDAVCache.MaxPermissionEntries, check.Equals, 100)
461         c.Check(cfg.cluster.Collections.WebDAVCache.MaxUUIDEntries, check.Equals, 100)
462
463         c.Check(cfg.cluster.Services.WebDAVDownload.ExternalURL, check.Equals, arvados.URL{Host: "download.example.com"})
464         c.Check(cfg.cluster.Services.WebDAVDownload.InternalURLs[arvados.URL{Host: ":80"}], check.NotNil)
465         c.Check(cfg.cluster.Services.WebDAV.InternalURLs[arvados.URL{Host: ":80"}], check.NotNil)
466
467         c.Check(cfg.cluster.Collections.TrustAllContent, check.Equals, true)
468         c.Check(cfg.cluster.Users.AnonymousUserToken, check.Equals, "anonusertoken")
469         c.Check(cfg.cluster.ManagementToken, check.Equals, "xyzzy")
470 }
471
472 func (s *IntegrationSuite) SetUpSuite(c *check.C) {
473         arvadostest.StartAPI()
474         arvadostest.StartKeep(2, true)
475
476         arv, err := arvadosclient.MakeArvadosClient()
477         c.Assert(err, check.Equals, nil)
478         arv.ApiToken = arvadostest.ActiveToken
479         kc, err := keepclient.MakeKeepClient(arv)
480         c.Assert(err, check.Equals, nil)
481         kc.PutB([]byte("Hello world\n"))
482         kc.PutB([]byte("foo"))
483         kc.PutB([]byte("foobar"))
484         kc.PutB([]byte("waz"))
485 }
486
487 func (s *IntegrationSuite) TearDownSuite(c *check.C) {
488         arvadostest.StopKeep(2)
489         arvadostest.StopAPI()
490 }
491
492 func (s *IntegrationSuite) SetUpTest(c *check.C) {
493         arvadostest.ResetEnv()
494         ldr := config.NewLoader(bytes.NewBufferString("Clusters: {zzzzz: {}}"), nil)
495         ldr.Path = "-"
496         arvCfg, err := ldr.Load()
497         c.Check(err, check.IsNil)
498         cfg := DefaultConfig(arvCfg)
499         c.Assert(err, check.IsNil)
500         cfg.Client = arvados.Client{
501                 APIHost:  testAPIHost,
502                 Insecure: true,
503         }
504         listen := "127.0.0.1:0"
505         cfg.cluster.Services.WebDAV.InternalURLs[arvados.URL{Host: listen}] = arvados.ServiceInstance{}
506         cfg.cluster.Services.WebDAVDownload.InternalURLs[arvados.URL{Host: listen}] = arvados.ServiceInstance{}
507         cfg.cluster.ManagementToken = arvadostest.ManagementToken
508         cfg.cluster.Users.AnonymousUserToken = arvadostest.AnonymousToken
509         s.testServer = &server{Config: cfg}
510         err = s.testServer.Start()
511         c.Assert(err, check.Equals, nil)
512 }
513
514 func (s *IntegrationSuite) TearDownTest(c *check.C) {
515         var err error
516         if s.testServer != nil {
517                 err = s.testServer.Close()
518         }
519         c.Check(err, check.Equals, nil)
520 }
521
522 // Gocheck boilerplate
523 func Test(t *testing.T) {
524         check.TestingT(t)
525 }