Merge branch '8784-dir-listings'
[arvados.git] / sdk / go / manifest / manifest_test.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: Apache-2.0
4
5 package manifest
6
7 import (
8         "fmt"
9         "git.curoverse.com/arvados.git/sdk/go/arvadostest"
10         "git.curoverse.com/arvados.git/sdk/go/blockdigest"
11         "io/ioutil"
12         "reflect"
13         "regexp"
14         "runtime"
15         "testing"
16 )
17
18 func getStackTrace() string {
19         buf := make([]byte, 1000)
20         bytesWritten := runtime.Stack(buf, false)
21         return "Stack Trace:\n" + string(buf[:bytesWritten])
22 }
23
24 func expectFromChannel(t *testing.T, c <-chan string, expected string) {
25         actual, ok := <-c
26         if !ok {
27                 t.Fatalf("Expected to receive %s but channel was closed. %s",
28                         expected,
29                         getStackTrace())
30         }
31         if actual != expected {
32                 t.Fatalf("Expected %s but got %s instead. %s",
33                         expected,
34                         actual,
35                         getStackTrace())
36         }
37 }
38
39 func expectChannelClosed(t *testing.T, c <-chan interface{}) {
40         received, ok := <-c
41         if ok {
42                 t.Fatalf("Expected channel to be closed, but received %v instead. %s",
43                         received,
44                         getStackTrace())
45         }
46 }
47
48 func expectEqual(t *testing.T, actual interface{}, expected interface{}) {
49         if actual != expected {
50                 t.Fatalf("Expected %v but received %v instead. %s",
51                         expected,
52                         actual,
53                         getStackTrace())
54         }
55 }
56
57 func expectStringSlicesEqual(t *testing.T, actual []string, expected []string) {
58         if len(actual) != len(expected) {
59                 t.Fatalf("Expected %v (length %d), but received %v (length %d) instead. %s", expected, len(expected), actual, len(actual), getStackTrace())
60         }
61         for i := range actual {
62                 if actual[i] != expected[i] {
63                         t.Fatalf("Expected %v but received %v instead (first disagreement at position %d). %s", expected, actual, i, getStackTrace())
64                 }
65         }
66 }
67
68 func expectFileStreamSegmentsEqual(t *testing.T, actual []FileStreamSegment, expected []FileStreamSegment) {
69         if !reflect.DeepEqual(actual, expected) {
70                 t.Fatalf("Expected %v but received %v instead. %s", expected, actual, getStackTrace())
71         }
72 }
73
74 func expectManifestStream(t *testing.T, actual ManifestStream, expected ManifestStream) {
75         expectEqual(t, actual.StreamName, expected.StreamName)
76         expectStringSlicesEqual(t, actual.Blocks, expected.Blocks)
77         expectFileStreamSegmentsEqual(t, actual.FileStreamSegments, expected.FileStreamSegments)
78 }
79
80 func expectBlockLocator(t *testing.T, actual blockdigest.BlockLocator, expected blockdigest.BlockLocator) {
81         expectEqual(t, actual.Digest, expected.Digest)
82         expectEqual(t, actual.Size, expected.Size)
83         expectStringSlicesEqual(t, actual.Hints, expected.Hints)
84 }
85
86 func TestParseManifestStreamSimple(t *testing.T) {
87         m := parseManifestStream(". 365f83f5f808896ec834c8b595288735+2310+K@qr1hi+Af0c9a66381f3b028677411926f0be1c6282fe67c@542b5ddf 0:2310:qr1hi-8i9sb-ienvmpve1a0vpoi.log.txt")
88         expectManifestStream(t, m, ManifestStream{StreamName: ".",
89                 Blocks:             []string{"365f83f5f808896ec834c8b595288735+2310+K@qr1hi+Af0c9a66381f3b028677411926f0be1c6282fe67c@542b5ddf"},
90                 FileStreamSegments: []FileStreamSegment{{0, 2310, "qr1hi-8i9sb-ienvmpve1a0vpoi.log.txt"}}})
91 }
92
93 func TestParseBlockLocatorSimple(t *testing.T) {
94         b, err := ParseBlockLocator("365f83f5f808896ec834c8b595288735+2310+K@qr1hi+Af0c9a66381f3b028677411926f0be1c6282fe67c@542b5ddf")
95         if err != nil {
96                 t.Fatalf("Unexpected error parsing block locator: %v", err)
97         }
98         d, err := blockdigest.FromString("365f83f5f808896ec834c8b595288735")
99         if err != nil {
100                 t.Fatalf("Unexpected error during FromString for block locator: %v", err)
101         }
102         expectBlockLocator(t, blockdigest.BlockLocator{b.Digest, b.Size, b.Hints},
103                 blockdigest.BlockLocator{Digest: d,
104                         Size: 2310,
105                         Hints: []string{"K@qr1hi",
106                                 "Af0c9a66381f3b028677411926f0be1c6282fe67c@542b5ddf"}})
107 }
108
109 func TestStreamIterShortManifestWithBlankStreams(t *testing.T) {
110         content, err := ioutil.ReadFile("testdata/short_manifest")
111         if err != nil {
112                 t.Fatalf("Unexpected error reading manifest from file: %v", err)
113         }
114         manifest := Manifest{Text: string(content)}
115         streamIter := manifest.StreamIter()
116
117         firstStream := <-streamIter
118         expectManifestStream(t,
119                 firstStream,
120                 ManifestStream{StreamName: ".",
121                         Blocks:             []string{"b746e3d2104645f2f64cd3cc69dd895d+15693477+E2866e643690156651c03d876e638e674dcd79475@5441920c"},
122                         FileStreamSegments: []FileStreamSegment{{0, 15693477, "chr10_band0_s0_e3000000.fj"}}})
123
124         received, ok := <-streamIter
125         if ok {
126                 t.Fatalf("Expected streamIter to be closed, but received %v instead.",
127                         received)
128         }
129 }
130
131 func TestBlockIterLongManifest(t *testing.T) {
132         content, err := ioutil.ReadFile("testdata/long_manifest")
133         if err != nil {
134                 t.Fatalf("Unexpected error reading manifest from file: %v", err)
135         }
136         manifest := Manifest{Text: string(content)}
137         blockChannel := manifest.BlockIterWithDuplicates()
138
139         firstBlock := <-blockChannel
140         d, err := blockdigest.FromString("b746e3d2104645f2f64cd3cc69dd895d")
141         if err != nil {
142                 t.Fatalf("Unexpected error during FromString for block: %v", err)
143         }
144         expectBlockLocator(t,
145                 firstBlock,
146                 blockdigest.BlockLocator{Digest: d,
147                         Size:  15693477,
148                         Hints: []string{"E2866e643690156651c03d876e638e674dcd79475@5441920c"}})
149         blocksRead := 1
150         var lastBlock blockdigest.BlockLocator
151         for lastBlock = range blockChannel {
152                 blocksRead++
153         }
154         expectEqual(t, blocksRead, 853)
155
156         d, err = blockdigest.FromString("f9ce82f59e5908d2d70e18df9679b469")
157         if err != nil {
158                 t.Fatalf("Unexpected error during FromString for block: %v", err)
159         }
160         expectBlockLocator(t,
161                 lastBlock,
162                 blockdigest.BlockLocator{Digest: d,
163                         Size:  31367794,
164                         Hints: []string{"E53f903684239bcc114f7bf8ff9bd6089f33058db@5441920c"}})
165 }
166
167 func TestUnescape(t *testing.T) {
168         for _, testCase := range [][]string{
169                 {`\040`, ` `},
170                 {`\009`, `\009`},
171                 {`\\\040\\`, `\ \`},
172                 {`\\040\`, `\040\`},
173         } {
174                 in := testCase[0]
175                 expect := testCase[1]
176                 got := UnescapeName(in)
177                 if expect != got {
178                         t.Errorf("For '%s' got '%s' instead of '%s'", in, got, expect)
179                 }
180         }
181 }
182
183 type fsegtest struct {
184         mt   string        // manifest text
185         f    string        // filename
186         want []FileSegment // segments should be received on channel
187 }
188
189 func TestFileSegmentIterByName(t *testing.T) {
190         mt := arvadostest.PathologicalManifest
191         for _, testCase := range []fsegtest{
192                 {mt: mt, f: "zzzz", want: nil},
193                 // This case is too sensitive: it would be acceptable
194                 // (even preferable) to return only one empty segment.
195                 {mt: mt, f: "foo/zero", want: []FileSegment{{"d41d8cd98f00b204e9800998ecf8427e+0", 0, 0}, {"d41d8cd98f00b204e9800998ecf8427e+0", 0, 0}}},
196                 {mt: mt, f: "zero@0", want: []FileSegment{{"d41d8cd98f00b204e9800998ecf8427e+0", 0, 0}}},
197                 {mt: mt, f: "zero@1", want: []FileSegment{{"d41d8cd98f00b204e9800998ecf8427e+0", 0, 0}}},
198                 {mt: mt, f: "zero@4", want: []FileSegment{{"d41d8cd98f00b204e9800998ecf8427e+0", 0, 0}}},
199                 {mt: mt, f: "zero@9", want: []FileSegment{{"d41d8cd98f00b204e9800998ecf8427e+0", 0, 0}}},
200                 {mt: mt, f: "f", want: []FileSegment{{"acbd18db4cc2f85cedef654fccc4a4d8+3", 0, 1}}},
201                 {mt: mt, f: "ooba", want: []FileSegment{{"acbd18db4cc2f85cedef654fccc4a4d8+3", 1, 2}, {"37b51d194a7513e45b56f6524f2d51f2+3", 0, 2}}},
202                 {mt: mt, f: "overlapReverse/o", want: []FileSegment{{"acbd18db4cc2f85cedef654fccc4a4d8+3", 2, 1}}},
203                 {mt: mt, f: "overlapReverse/oo", want: []FileSegment{{"acbd18db4cc2f85cedef654fccc4a4d8+3", 1, 2}}},
204                 {mt: mt, f: "overlapReverse/ofoo", want: []FileSegment{{"acbd18db4cc2f85cedef654fccc4a4d8+3", 2, 1}, {"acbd18db4cc2f85cedef654fccc4a4d8+3", 0, 3}}},
205                 {mt: mt, f: "foo bar/baz", want: []FileSegment{{"acbd18db4cc2f85cedef654fccc4a4d8+3", 0, 3}}},
206                 // This case is too sensitive: it would be better to
207                 // omit the empty segment.
208                 {mt: mt, f: "segmented/frob", want: []FileSegment{{"acbd18db4cc2f85cedef654fccc4a4d8+3", 0, 1}, {"37b51d194a7513e45b56f6524f2d51f2+3", 2, 1}, {"acbd18db4cc2f85cedef654fccc4a4d8+3", 1, 1}, {"d41d8cd98f00b204e9800998ecf8427e+0", 0, 0}, {"37b51d194a7513e45b56f6524f2d51f2+3", 0, 1}}},
209                 {mt: mt, f: "segmented/oof", want: []FileSegment{{"acbd18db4cc2f85cedef654fccc4a4d8+3", 1, 2}, {"acbd18db4cc2f85cedef654fccc4a4d8+3", 0, 1}}},
210         } {
211                 m := Manifest{Text: testCase.mt}
212                 var got []FileSegment
213                 for fs := range m.FileSegmentIterByName(testCase.f) {
214                         got = append(got, *fs)
215                 }
216                 if !reflect.DeepEqual(got, testCase.want) {
217                         t.Errorf("For %#v:\n got  %#v\n want %#v", testCase.f, got, testCase.want)
218                 }
219         }
220 }
221
222 func TestBlockIterWithBadManifest(t *testing.T) {
223         testCases := [][]string{
224                 {"badstream acbd18db4cc2f85cedef654fccc4a4d8+3 0:1:file1.txt", "Invalid stream name: badstream"},
225                 {"/badstream acbd18db4cc2f85cedef654fccc4a4d8+3 0:1:file1.txt", "Invalid stream name: /badstream"},
226                 {". acbd18db4cc2f85cedef654fccc4a4d8+3 file1.txt", "Invalid file token: file1.txt"},
227                 {". acbd18db4cc2f85cedef654fccc4a4+3 0:1:file1.txt", "No block locators found"},
228                 {". acbd18db4cc2f85cedef654fccc4a4d8 0:1:file1.txt", "No block locators found"},
229                 {". acbd18db4cc2f85cedef654fccc4a4d8+3 0:1:file1.txt file2.txt 1:2:file3.txt", "Invalid file token: file2.txt"},
230                 {". acbd18db4cc2f85cedef654fccc4a4d8+3 0:1:file1.txt. bcde18db4cc2f85cedef654fccc4a4d8+3 1:2:file3.txt", "Invalid file token: bcde18db4cc2f85cedef654fccc4a4d8.*"},
231                 {". acbd18db4cc2f85cedef654fccc4a4d8+3 0:1:file1.txt\n. acbd18db4cc2f85cedef654fccc4a4d8+3 ::file2.txt\n", "Invalid file token: ::file2.txt"},
232                 {". acbd18db4cc2f85cedef654fccc4a4d8+3 bcde18db4cc2f85cedef654fccc4a4d8+3\n", "No file tokens found"},
233                 {". acbd18db4cc2f85cedef654fccc4a4d8+3 ", "Invalid file token"},
234                 {". acbd18db4cc2f85cedef654fccc4a4d8+3", "No file tokens found"},
235                 {". 0:1:file1.txt\n", "No block locators found"},
236                 {".\n", "No block locators found"},
237         }
238
239         for _, testCase := range testCases {
240                 manifest := Manifest{Text: string(testCase[0])}
241                 blockChannel := manifest.BlockIterWithDuplicates()
242
243                 for block := range blockChannel {
244                         _ = block
245                 }
246
247                 // completed reading from blockChannel; now check for errors
248                 if manifest.Err == nil {
249                         t.Fatalf("Expected error")
250                 }
251
252                 matched, _ := regexp.MatchString(testCase[1], manifest.Err.Error())
253                 if !matched {
254                         t.Fatalf("Expected error not found. Expected: %v; Found: %v", testCase[1], manifest.Err.Error())
255                 }
256         }
257 }
258
259 func TestNormalizeManifest(t *testing.T) {
260         m1 := Manifest{Text: `. 5348b82a029fd9e971a811ce1f71360b+43 0:43:md5sum.txt
261 . 085c37f02916da1cad16f93c54d899b7+41 0:41:md5sum.txt
262 . 8b22da26f9f433dea0a10e5ec66d73ba+43 0:43:md5sum.txt
263 `}
264         expectEqual(t, m1.Extract(".", ".").Text,
265                 `. 5348b82a029fd9e971a811ce1f71360b+43 085c37f02916da1cad16f93c54d899b7+41 8b22da26f9f433dea0a10e5ec66d73ba+43 0:127:md5sum.txt
266 `)
267
268         m2 := Manifest{Text: `. 204e43b8a1185621ca55a94839582e6f+67108864 b9677abbac956bd3e86b1deb28dfac03+67108864 fc15aff2a762b13f521baf042140acec+67108864 323d2a3ce20370c4ca1d3462a344f8fd+25885655 0:227212247:var-GS000016015-ASM.tsv.bz2
269 `}
270         expectEqual(t, m2.Extract(".", ".").Text, m2.Text)
271
272         m3 := Manifest{Text: `. 5348b82a029fd9e971a811ce1f71360b+43 3:40:md5sum.txt
273 . 085c37f02916da1cad16f93c54d899b7+41 0:41:md5sum.txt
274 . 8b22da26f9f433dea0a10e5ec66d73ba+43 0:43:md5sum.txt
275 `}
276         expectEqual(t, m3.Extract(".", ".").Text, `. 5348b82a029fd9e971a811ce1f71360b+43 085c37f02916da1cad16f93c54d899b7+41 8b22da26f9f433dea0a10e5ec66d73ba+43 3:124:md5sum.txt
277 `)
278         expectEqual(t, m3.Extract("/md5sum.txt", "/wiggle.txt").Text, `. 5348b82a029fd9e971a811ce1f71360b+43 085c37f02916da1cad16f93c54d899b7+41 8b22da26f9f433dea0a10e5ec66d73ba+43 3:124:wiggle.txt
279 `)
280
281         m4 := Manifest{Text: `. 204e43b8a1185621ca55a94839582e6f+67108864 0:3:foo/bar
282 ./zzz 204e43b8a1185621ca55a94839582e6f+67108864 0:999:zzz
283 ./foo 323d2a3ce20370c4ca1d3462a344f8fd+25885655 0:3:bar
284 `}
285
286         expectEqual(t, m4.Extract(".", ".").Text,
287                 `./foo 204e43b8a1185621ca55a94839582e6f+67108864 323d2a3ce20370c4ca1d3462a344f8fd+25885655 0:3:bar 67108864:3:bar
288 ./zzz 204e43b8a1185621ca55a94839582e6f+67108864 0:999:zzz
289 `)
290
291         expectEqual(t, m4.Extract("./foo", ".").Text, ". 204e43b8a1185621ca55a94839582e6f+67108864 323d2a3ce20370c4ca1d3462a344f8fd+25885655 0:3:bar 67108864:3:bar\n")
292         expectEqual(t, m4.Extract("./foo", "./baz").Text, "./baz 204e43b8a1185621ca55a94839582e6f+67108864 323d2a3ce20370c4ca1d3462a344f8fd+25885655 0:3:bar 67108864:3:bar\n")
293         expectEqual(t, m4.Extract("./foo/bar", ".").Text, ". 204e43b8a1185621ca55a94839582e6f+67108864 323d2a3ce20370c4ca1d3462a344f8fd+25885655 0:3:bar 67108864:3:bar\n")
294         expectEqual(t, m4.Extract("./foo/bar", "./baz").Text, ". 204e43b8a1185621ca55a94839582e6f+67108864 323d2a3ce20370c4ca1d3462a344f8fd+25885655 0:3:baz 67108864:3:baz\n")
295         expectEqual(t, m4.Extract("./foo/bar", "./quux/").Text, "./quux 204e43b8a1185621ca55a94839582e6f+67108864 323d2a3ce20370c4ca1d3462a344f8fd+25885655 0:3:bar 67108864:3:bar\n")
296         expectEqual(t, m4.Extract("./foo/bar", "./quux/baz").Text, "./quux 204e43b8a1185621ca55a94839582e6f+67108864 323d2a3ce20370c4ca1d3462a344f8fd+25885655 0:3:baz 67108864:3:baz\n")
297         expectEqual(t, m4.Extract(".", ".").Text, `./foo 204e43b8a1185621ca55a94839582e6f+67108864 323d2a3ce20370c4ca1d3462a344f8fd+25885655 0:3:bar 67108864:3:bar
298 ./zzz 204e43b8a1185621ca55a94839582e6f+67108864 0:999:zzz
299 `)
300         expectEqual(t, m4.Extract(".", "./zip").Text, `./zip/foo 204e43b8a1185621ca55a94839582e6f+67108864 323d2a3ce20370c4ca1d3462a344f8fd+25885655 0:3:bar 67108864:3:bar
301 ./zip/zzz 204e43b8a1185621ca55a94839582e6f+67108864 0:999:zzz
302 `)
303
304         expectEqual(t, m4.Extract("foo/.//bar/../../zzz/", "/waz/").Text, `./waz 204e43b8a1185621ca55a94839582e6f+67108864 0:999:zzz
305 `)
306
307         m5 := Manifest{Text: `. 204e43b8a1185621ca55a94839582e6f+67108864 0:3:foo/bar
308 ./zzz 204e43b8a1185621ca55a94839582e6f+67108864 0:999:zzz
309 ./foo 204e43b8a1185621ca55a94839582e6f+67108864 3:3:bar
310 `}
311         expectEqual(t, m5.Extract(".", ".").Text,
312                 `./foo 204e43b8a1185621ca55a94839582e6f+67108864 0:6:bar
313 ./zzz 204e43b8a1185621ca55a94839582e6f+67108864 0:999:zzz
314 `)
315
316         m8 := Manifest{Text: `./a\040b\040c 59ca0efa9f5633cb0371bbc0355478d8+13 0:13:hello\040world.txt
317 `}
318         expectEqual(t, m8.Extract(".", ".").Text, m8.Text)
319
320         m9 := Manifest{Text: ". acbd18db4cc2f85cedef654fccc4a4d8+40 0:10:one 20:10:two 10:10:one 30:10:two\n"}
321         expectEqual(t, m9.Extract("", "").Text, ". acbd18db4cc2f85cedef654fccc4a4d8+40 0:20:one 20:20:two\n")
322
323         m10 := Manifest{Text: ". acbd18db4cc2f85cedef654fccc4a4d8+40 0:10:one 20:10:two 10:10:one 30:10:two\n"}
324         expectEqual(t, m10.Extract("./two", "./three").Text, ". acbd18db4cc2f85cedef654fccc4a4d8+40 20:20:three\n")
325
326         m11 := Manifest{Text: arvadostest.PathologicalManifest}
327         expectEqual(t, m11.Extract(".", ".").Text, `. acbd18db4cc2f85cedef654fccc4a4d8+3 37b51d194a7513e45b56f6524f2d51f2+3 73feffa4b7f6bb68e44cf984c85f6e88+3+Z+K@xyzzy 0:1:f 1:4:ooba 5:1:r 5:4:rbaz 0:0:zero@0 0:0:zero@1 0:0:zero@4 0:0:zero@9
328 ./foo acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:foo 0:3:foo 0:0:zero
329 ./foo\040bar acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:baz 0:3:baz\040waz
330 ./overlapReverse acbd18db4cc2f85cedef654fccc4a4d8+3 2:1:o 2:1:ofoo 0:3:ofoo 1:2:oo
331 ./segmented acbd18db4cc2f85cedef654fccc4a4d8+3 37b51d194a7513e45b56f6524f2d51f2+3 0:1:frob 5:1:frob 1:1:frob 3:1:frob 1:2:oof 0:1:oof
332 `)
333
334         m12 := Manifest{Text: `./foo 204e43b8a1185621ca55a94839582e6f+67108864 0:3:bar
335 ./zzz 204e43b8a1185621ca55a94839582e6f+67108864 0:999:zzz
336 ./foo/baz 323d2a3ce20370c4ca1d3462a344f8fd+25885655 0:3:bar
337 `}
338
339         expectEqual(t, m12.Extract("./foo", ".").Text, `. 204e43b8a1185621ca55a94839582e6f+67108864 0:3:bar
340 ./baz 323d2a3ce20370c4ca1d3462a344f8fd+25885655 0:3:bar
341 `)
342         expectEqual(t, m12.Extract("./foo", "./blub").Text, `./blub 204e43b8a1185621ca55a94839582e6f+67108864 0:3:bar
343 ./blub/baz 323d2a3ce20370c4ca1d3462a344f8fd+25885655 0:3:bar
344 `)
345         expectEqual(t, m12.Extract("./foo", "./blub/").Text, `./blub 204e43b8a1185621ca55a94839582e6f+67108864 0:3:bar
346 ./blub/baz 323d2a3ce20370c4ca1d3462a344f8fd+25885655 0:3:bar
347 `)
348         expectEqual(t, m12.Extract("./foo/", "./blub/").Text, `./blub 204e43b8a1185621ca55a94839582e6f+67108864 0:3:bar
349 ./blub/baz 323d2a3ce20370c4ca1d3462a344f8fd+25885655 0:3:bar
350 `)
351
352         m13 := Manifest{Text: `foo 204e43b8a1185621ca55a94839582e6f+67108864 0:3:bar
353 `}
354
355         expectEqual(t, m13.Extract(".", ".").Text, ``)
356         expectEqual(t, m13.Extract(".", ".").Err.Error(), "Invalid stream name: foo")
357
358         m14 := Manifest{Text: `./foo 204e43b8a1185621ca55a94839582e6f+67108864 67108863:3:bar
359 `}
360
361         expectEqual(t, m14.Extract(".", ".").Text, ``)
362         expectEqual(t, m14.Extract(".", ".").Err.Error(), "File segment 67108863:3:bar extends past end of stream 67108864")
363
364         m15 := Manifest{Text: `./foo 204e43b8a1185621ca55a94839582e6f+67108864 0:3bar
365 `}
366
367         expectEqual(t, m15.Extract(".", ".").Text, ``)
368         expectEqual(t, m15.Extract(".", ".").Err.Error(), "Invalid file token: 0:3bar")
369 }
370
371 func TestFirstBlock(t *testing.T) {
372         fmt.Println("ZZZ")
373         expectEqual(t, firstBlock([]uint64{1, 2, 3, 4}, 3), 2)
374         expectEqual(t, firstBlock([]uint64{1, 2, 3, 4, 5, 6}, 4), 3)
375 }