View Javadoc
1   /*
2    * Copyright (C) 2017, Google Inc.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  
44  package org.eclipse.jgit.internal.storage.reftable;
45  
46  import static org.eclipse.jgit.lib.Constants.HEAD;
47  import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
48  import static org.eclipse.jgit.lib.Constants.R_HEADS;
49  import static org.eclipse.jgit.lib.Ref.Storage.NEW;
50  import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
51  import static org.junit.Assert.assertEquals;
52  import static org.junit.Assert.assertFalse;
53  import static org.junit.Assert.assertNotNull;
54  import static org.junit.Assert.assertNull;
55  import static org.junit.Assert.assertSame;
56  import static org.junit.Assert.assertTrue;
57  import static org.junit.Assert.fail;
58  
59  import java.io.ByteArrayOutputStream;
60  import java.io.IOException;
61  import java.util.ArrayList;
62  import java.util.Arrays;
63  import java.util.Collection;
64  import java.util.Collections;
65  import java.util.List;
66  
67  import org.eclipse.jgit.internal.JGitText;
68  import org.eclipse.jgit.internal.storage.io.BlockSource;
69  import org.eclipse.jgit.internal.storage.reftable.ReftableWriter.Stats;
70  import org.eclipse.jgit.lib.ObjectId;
71  import org.eclipse.jgit.lib.ObjectIdRef;
72  import org.eclipse.jgit.lib.PersonIdent;
73  import org.eclipse.jgit.lib.Ref;
74  import org.eclipse.jgit.lib.ReflogEntry;
75  import org.eclipse.jgit.lib.SymbolicRef;
76  import org.junit.Test;
77  
78  public class ReftableTest {
79  	private static final String MASTER = "refs/heads/master";
80  	private static final String NEXT = "refs/heads/next";
81  	private static final String V1_0 = "refs/tags/v1.0";
82  
83  	private Stats stats;
84  
85  	@Test
86  	public void emptyTable() throws IOException {
87  		byte[] table = write();
88  		assertEquals(92 /* header, footer */, table.length);
89  		assertEquals('R', table[0]);
90  		assertEquals('E', table[1]);
91  		assertEquals('F', table[2]);
92  		assertEquals('T', table[3]);
93  		assertEquals(0x01, table[4]);
94  		assertTrue(ReftableConstants.isFileHeaderMagic(table, 0, 8));
95  		assertTrue(ReftableConstants.isFileHeaderMagic(table, 24, 92));
96  
97  		Reftable t = read(table);
98  		try (RefCursor rc = t.allRefs()) {
99  			assertFalse(rc.next());
100 		}
101 		try (RefCursor rc = t.seekRef(HEAD)) {
102 			assertFalse(rc.next());
103 		}
104 		try (RefCursor rc = t.seekRefsWithPrefix(R_HEADS)) {
105 			assertFalse(rc.next());
106 		}
107 		try (LogCursor rc = t.allLogs()) {
108 			assertFalse(rc.next());
109 		}
110 	}
111 
112 	@Test
113 	public void emptyVirtualTableFromRefs() throws IOException {
114 		Reftable t = Reftable.from(Collections.emptyList());
115 		try (RefCursor rc = t.allRefs()) {
116 			assertFalse(rc.next());
117 		}
118 		try (RefCursor rc = t.seekRef(HEAD)) {
119 			assertFalse(rc.next());
120 		}
121 		try (LogCursor rc = t.allLogs()) {
122 			assertFalse(rc.next());
123 		}
124 	}
125 
126 	@Test
127 	public void estimateCurrentBytesOneRef() throws IOException {
128 		Ref exp = ref(MASTER, 1);
129 		int expBytes = 24 + 4 + 5 + 4 + MASTER.length() + 20 + 68;
130 
131 		byte[] table;
132 		ReftableConfig cfg = new ReftableConfig();
133 		cfg.setIndexObjects(false);
134 		ReftableWriter writer = new ReftableWriter().setConfig(cfg);
135 		try (ByteArrayOutputStream buf = new ByteArrayOutputStream()) {
136 			writer.begin(buf);
137 			assertEquals(92, writer.estimateTotalBytes());
138 			writer.writeRef(exp);
139 			assertEquals(expBytes, writer.estimateTotalBytes());
140 			writer.finish();
141 			table = buf.toByteArray();
142 		}
143 		assertEquals(expBytes, table.length);
144 	}
145 
146 	@SuppressWarnings("boxing")
147 	@Test
148 	public void estimateCurrentBytesWithIndex() throws IOException {
149 		List<Ref> refs = new ArrayList<>();
150 		for (int i = 1; i <= 5670; i++) {
151 			refs.add(ref(String.format("refs/heads/%04d", i), i));
152 		}
153 
154 		ReftableConfig cfg = new ReftableConfig();
155 		cfg.setIndexObjects(false);
156 		cfg.setMaxIndexLevels(1);
157 
158 		int expBytes = 147860;
159 		byte[] table;
160 		ReftableWriter writer = new ReftableWriter().setConfig(cfg);
161 		try (ByteArrayOutputStream buf = new ByteArrayOutputStream()) {
162 			writer.begin(buf);
163 			writer.sortAndWriteRefs(refs);
164 			assertEquals(expBytes, writer.estimateTotalBytes());
165 			writer.finish();
166 			stats = writer.getStats();
167 			table = buf.toByteArray();
168 		}
169 		assertEquals(1, stats.refIndexLevels());
170 		assertEquals(expBytes, table.length);
171 	}
172 
173 	@Test
174 	public void oneIdRef() throws IOException {
175 		Ref exp = ref(MASTER, 1);
176 		byte[] table = write(exp);
177 		assertEquals(24 + 4 + 5 + 4 + MASTER.length() + 20 + 68, table.length);
178 
179 		ReftableReader t = read(table);
180 		try (RefCursor rc = t.allRefs()) {
181 			assertTrue(rc.next());
182 			Ref act = rc.getRef();
183 			assertNotNull(act);
184 			assertEquals(PACKED, act.getStorage());
185 			assertTrue(act.isPeeled());
186 			assertFalse(act.isSymbolic());
187 			assertEquals(exp.getName(), act.getName());
188 			assertEquals(exp.getObjectId(), act.getObjectId());
189 			assertNull(act.getPeeledObjectId());
190 			assertFalse(rc.wasDeleted());
191 			assertFalse(rc.next());
192 		}
193 		try (RefCursor rc = t.seekRef(MASTER)) {
194 			assertTrue(rc.next());
195 			Ref act = rc.getRef();
196 			assertNotNull(act);
197 			assertEquals(exp.getName(), act.getName());
198 			assertFalse(rc.next());
199 		}
200 	}
201 
202 	@Test
203 	public void oneTagRef() throws IOException {
204 		Ref exp = tag(V1_0, 1, 2);
205 		byte[] table = write(exp);
206 		assertEquals(24 + 4 + 5 + 3 + V1_0.length() + 40 + 68, table.length);
207 
208 		ReftableReader t = read(table);
209 		try (RefCursor rc = t.allRefs()) {
210 			assertTrue(rc.next());
211 			Ref act = rc.getRef();
212 			assertNotNull(act);
213 			assertEquals(PACKED, act.getStorage());
214 			assertTrue(act.isPeeled());
215 			assertFalse(act.isSymbolic());
216 			assertEquals(exp.getName(), act.getName());
217 			assertEquals(exp.getObjectId(), act.getObjectId());
218 			assertEquals(exp.getPeeledObjectId(), act.getPeeledObjectId());
219 		}
220 	}
221 
222 	@Test
223 	public void oneSymbolicRef() throws IOException {
224 		Ref exp = sym(HEAD, MASTER);
225 		byte[] table = write(exp);
226 		assertEquals(
227 				24 + 4 + 5 + 2 + HEAD.length() + 2 + MASTER.length() + 68,
228 				table.length);
229 
230 		ReftableReader t = read(table);
231 		try (RefCursor rc = t.allRefs()) {
232 			assertTrue(rc.next());
233 			Ref act = rc.getRef();
234 			assertNotNull(act);
235 			assertTrue(act.isSymbolic());
236 			assertEquals(exp.getName(), act.getName());
237 			assertNotNull(act.getLeaf());
238 			assertEquals(MASTER, act.getTarget().getName());
239 			assertNull(act.getObjectId());
240 		}
241 	}
242 
243 	@Test
244 	public void resolveSymbolicRef() throws IOException {
245 		Reftable t = read(write(
246 				sym(HEAD, "refs/heads/tmp"),
247 				sym("refs/heads/tmp", MASTER),
248 				ref(MASTER, 1)));
249 
250 		Ref head = t.exactRef(HEAD);
251 		assertNull(head.getObjectId());
252 		assertEquals("refs/heads/tmp", head.getTarget().getName());
253 
254 		head = t.resolve(head);
255 		assertNotNull(head);
256 		assertEquals(id(1), head.getObjectId());
257 
258 		Ref master = t.exactRef(MASTER);
259 		assertNotNull(master);
260 		assertSame(master, t.resolve(master));
261 	}
262 
263 	@Test
264 	public void failDeepChainOfSymbolicRef() throws IOException {
265 		Reftable t = read(write(
266 				sym(HEAD, "refs/heads/1"),
267 				sym("refs/heads/1", "refs/heads/2"),
268 				sym("refs/heads/2", "refs/heads/3"),
269 				sym("refs/heads/3", "refs/heads/4"),
270 				sym("refs/heads/4", "refs/heads/5"),
271 				sym("refs/heads/5", MASTER),
272 				ref(MASTER, 1)));
273 
274 		Ref head = t.exactRef(HEAD);
275 		assertNull(head.getObjectId());
276 		assertNull(t.resolve(head));
277 	}
278 
279 	@Test
280 	public void oneDeletedRef() throws IOException {
281 		String name = "refs/heads/gone";
282 		Ref exp = newRef(name);
283 		byte[] table = write(exp);
284 		assertEquals(24 + 4 + 5 + 3 + name.length() + 68, table.length);
285 
286 		ReftableReader t = read(table);
287 		try (RefCursor rc = t.allRefs()) {
288 			assertFalse(rc.next());
289 		}
290 
291 		t.setIncludeDeletes(true);
292 		try (RefCursor rc = t.allRefs()) {
293 			assertTrue(rc.next());
294 			Ref act = rc.getRef();
295 			assertNotNull(act);
296 			assertFalse(act.isSymbolic());
297 			assertEquals(name, act.getName());
298 			assertEquals(NEW, act.getStorage());
299 			assertNull(act.getObjectId());
300 			assertTrue(rc.wasDeleted());
301 		}
302 	}
303 
304 	@Test
305 	public void seekNotFound() throws IOException {
306 		Ref exp = ref(MASTER, 1);
307 		ReftableReader t = read(write(exp));
308 		try (RefCursor rc = t.seekRef("refs/heads/a")) {
309 			assertFalse(rc.next());
310 		}
311 		try (RefCursor rc = t.seekRef("refs/heads/n")) {
312 			assertFalse(rc.next());
313 		}
314 	}
315 
316 	@Test
317 	public void namespaceNotFound() throws IOException {
318 		Ref exp = ref(MASTER, 1);
319 		ReftableReader t = read(write(exp));
320 		try (RefCursor rc = t.seekRefsWithPrefix("refs/changes/")) {
321 			assertFalse(rc.next());
322 		}
323 		try (RefCursor rc = t.seekRefsWithPrefix("refs/tags/")) {
324 			assertFalse(rc.next());
325 		}
326 	}
327 
328 	@Test
329 	public void namespaceHeads() throws IOException {
330 		Ref master = ref(MASTER, 1);
331 		Ref next = ref(NEXT, 2);
332 		Ref v1 = tag(V1_0, 3, 4);
333 
334 		ReftableReader t = read(write(master, next, v1));
335 		try (RefCursor rc = t.seekRefsWithPrefix("refs/tags/")) {
336 			assertTrue(rc.next());
337 			assertEquals(V1_0, rc.getRef().getName());
338 			assertFalse(rc.next());
339 		}
340 		try (RefCursor rc = t.seekRefsWithPrefix("refs/heads/")) {
341 			assertTrue(rc.next());
342 			assertEquals(MASTER, rc.getRef().getName());
343 
344 			assertTrue(rc.next());
345 			assertEquals(NEXT, rc.getRef().getName());
346 
347 			assertFalse(rc.next());
348 		}
349 	}
350 
351 	@SuppressWarnings("boxing")
352 	@Test
353 	public void indexScan() throws IOException {
354 		List<Ref> refs = new ArrayList<>();
355 		for (int i = 1; i <= 5670; i++) {
356 			refs.add(ref(String.format("refs/heads/%04d", i), i));
357 		}
358 
359 		byte[] table = write(refs);
360 		assertTrue(stats.refIndexLevels() > 0);
361 		assertTrue(stats.refIndexSize() > 0);
362 		assertScan(refs, read(table));
363 	}
364 
365 	@SuppressWarnings("boxing")
366 	@Test
367 	public void indexSeek() throws IOException {
368 		List<Ref> refs = new ArrayList<>();
369 		for (int i = 1; i <= 5670; i++) {
370 			refs.add(ref(String.format("refs/heads/%04d", i), i));
371 		}
372 
373 		byte[] table = write(refs);
374 		assertTrue(stats.refIndexLevels() > 0);
375 		assertTrue(stats.refIndexSize() > 0);
376 		assertSeek(refs, read(table));
377 	}
378 
379 	@SuppressWarnings("boxing")
380 	@Test
381 	public void noIndexScan() throws IOException {
382 		List<Ref> refs = new ArrayList<>();
383 		for (int i = 1; i <= 567; i++) {
384 			refs.add(ref(String.format("refs/heads/%03d", i), i));
385 		}
386 
387 		byte[] table = write(refs);
388 		assertEquals(0, stats.refIndexLevels());
389 		assertEquals(0, stats.refIndexSize());
390 		assertEquals(table.length, stats.totalBytes());
391 		assertScan(refs, read(table));
392 	}
393 
394 	@SuppressWarnings("boxing")
395 	@Test
396 	public void noIndexSeek() throws IOException {
397 		List<Ref> refs = new ArrayList<>();
398 		for (int i = 1; i <= 567; i++) {
399 			refs.add(ref(String.format("refs/heads/%03d", i), i));
400 		}
401 
402 		byte[] table = write(refs);
403 		assertEquals(0, stats.refIndexLevels());
404 		assertSeek(refs, read(table));
405 	}
406 
407 	@Test
408 	public void withReflog() throws IOException {
409 		Ref master = ref(MASTER, 1);
410 		Ref next = ref(NEXT, 2);
411 		PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
412 		String msg = "test";
413 
414 		ByteArrayOutputStream buffer = new ByteArrayOutputStream();
415 		ReftableWriter writer = new ReftableWriter()
416 				.setMinUpdateIndex(1)
417 				.setMaxUpdateIndex(1)
418 				.begin(buffer);
419 
420 		writer.writeRef(master);
421 		writer.writeRef(next);
422 
423 		writer.writeLog(MASTER, 1, who, ObjectId.zeroId(), id(1), msg);
424 		writer.writeLog(NEXT, 1, who, ObjectId.zeroId(), id(2), msg);
425 
426 		writer.finish();
427 		byte[] table = buffer.toByteArray();
428 		assertEquals(247, table.length);
429 
430 		ReftableReader t = read(table);
431 		try (RefCursor rc = t.allRefs()) {
432 			assertTrue(rc.next());
433 			assertEquals(MASTER, rc.getRef().getName());
434 			assertEquals(id(1), rc.getRef().getObjectId());
435 			assertEquals(1, rc.getUpdateIndex());
436 
437 			assertTrue(rc.next());
438 			assertEquals(NEXT, rc.getRef().getName());
439 			assertEquals(id(2), rc.getRef().getObjectId());
440 			assertFalse(rc.next());
441 		}
442 		try (LogCursor lc = t.allLogs()) {
443 			assertTrue(lc.next());
444 			assertEquals(MASTER, lc.getRefName());
445 			assertEquals(1, lc.getUpdateIndex());
446 			assertEquals(ObjectId.zeroId(), lc.getReflogEntry().getOldId());
447 			assertEquals(id(1), lc.getReflogEntry().getNewId());
448 			assertEquals(who, lc.getReflogEntry().getWho());
449 			assertEquals(msg, lc.getReflogEntry().getComment());
450 
451 			assertTrue(lc.next());
452 			assertEquals(NEXT, lc.getRefName());
453 			assertEquals(1, lc.getUpdateIndex());
454 			assertEquals(ObjectId.zeroId(), lc.getReflogEntry().getOldId());
455 			assertEquals(id(2), lc.getReflogEntry().getNewId());
456 			assertEquals(who, lc.getReflogEntry().getWho());
457 			assertEquals(msg, lc.getReflogEntry().getComment());
458 
459 			assertFalse(lc.next());
460 		}
461 	}
462 
463 	@Test
464 	public void onlyReflog() throws IOException {
465 		PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
466 		String msg = "test";
467 
468 		ByteArrayOutputStream buffer = new ByteArrayOutputStream();
469 		ReftableWriter writer = new ReftableWriter()
470 				.setMinUpdateIndex(1)
471 				.setMaxUpdateIndex(1)
472 				.begin(buffer);
473 		writer.writeLog(MASTER, 1, who, ObjectId.zeroId(), id(1), msg);
474 		writer.writeLog(NEXT, 1, who, ObjectId.zeroId(), id(2), msg);
475 		writer.finish();
476 		byte[] table = buffer.toByteArray();
477 		stats = writer.getStats();
478 		assertEquals(170, table.length);
479 		assertEquals(0, stats.refCount());
480 		assertEquals(0, stats.refBytes());
481 		assertEquals(0, stats.refIndexLevels());
482 
483 		ReftableReader t = read(table);
484 		try (RefCursor rc = t.allRefs()) {
485 			assertFalse(rc.next());
486 		}
487 		try (RefCursor rc = t.seekRefsWithPrefix("refs/heads/")) {
488 			assertFalse(rc.next());
489 		}
490 		try (LogCursor lc = t.allLogs()) {
491 			assertTrue(lc.next());
492 			assertEquals(MASTER, lc.getRefName());
493 			assertEquals(1, lc.getUpdateIndex());
494 			assertEquals(ObjectId.zeroId(), lc.getReflogEntry().getOldId());
495 			assertEquals(id(1), lc.getReflogEntry().getNewId());
496 			assertEquals(who, lc.getReflogEntry().getWho());
497 			assertEquals(msg, lc.getReflogEntry().getComment());
498 
499 			assertTrue(lc.next());
500 			assertEquals(NEXT, lc.getRefName());
501 			assertEquals(1, lc.getUpdateIndex());
502 			assertEquals(ObjectId.zeroId(), lc.getReflogEntry().getOldId());
503 			assertEquals(id(2), lc.getReflogEntry().getNewId());
504 			assertEquals(who, lc.getReflogEntry().getWho());
505 			assertEquals(msg, lc.getReflogEntry().getComment());
506 
507 			assertFalse(lc.next());
508 		}
509 	}
510 
511 	@SuppressWarnings("boxing")
512 	@Test
513 	public void logScan() throws IOException {
514 		ReftableConfig cfg = new ReftableConfig();
515 		cfg.setRefBlockSize(256);
516 		cfg.setLogBlockSize(2048);
517 
518 		ByteArrayOutputStream buffer = new ByteArrayOutputStream();
519 		ReftableWriter writer = new ReftableWriter(cfg);
520 		writer.setMinUpdateIndex(1).setMaxUpdateIndex(1).begin(buffer);
521 
522 		List<Ref> refs = new ArrayList<>();
523 		for (int i = 1; i <= 5670; i++) {
524 			Ref ref = ref(String.format("refs/heads/%03d", i), i);
525 			refs.add(ref);
526 			writer.writeRef(ref);
527 		}
528 
529 		PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
530 		for (Ref ref : refs) {
531 			writer.writeLog(ref.getName(), 1, who,
532 					ObjectId.zeroId(), ref.getObjectId(),
533 					"create " + ref.getName());
534 		}
535 		writer.finish();
536 		stats = writer.getStats();
537 		assertTrue(stats.logBytes() > 4096);
538 		byte[] table = buffer.toByteArray();
539 
540 		ReftableReader t = read(table);
541 		try (LogCursor lc = t.allLogs()) {
542 			for (Ref exp : refs) {
543 				assertTrue("has " + exp.getName(), lc.next());
544 				assertEquals(exp.getName(), lc.getRefName());
545 				ReflogEntry entry = lc.getReflogEntry();
546 				assertNotNull(entry);
547 				assertEquals(who, entry.getWho());
548 				assertEquals(ObjectId.zeroId(), entry.getOldId());
549 				assertEquals(exp.getObjectId(), entry.getNewId());
550 				assertEquals("create " + exp.getName(), entry.getComment());
551 			}
552 			assertFalse(lc.next());
553 		}
554 	}
555 
556 	@SuppressWarnings("boxing")
557 	@Test
558 	public void byObjectIdOneRefNoIndex() throws IOException {
559 		List<Ref> refs = new ArrayList<>();
560 		for (int i = 1; i <= 200; i++) {
561 			refs.add(ref(String.format("refs/heads/%02d", i), i));
562 		}
563 		refs.add(ref("refs/heads/master", 100));
564 
565 		ReftableReader t = read(write(refs));
566 		assertEquals(0, stats.objIndexSize());
567 
568 		try (RefCursor rc = t.byObjectId(id(42))) {
569 			assertTrue("has 42", rc.next());
570 			assertEquals("refs/heads/42", rc.getRef().getName());
571 			assertEquals(id(42), rc.getRef().getObjectId());
572 			assertFalse(rc.next());
573 		}
574 		try (RefCursor rc = t.byObjectId(id(100))) {
575 			assertTrue("has 100", rc.next());
576 			assertEquals("refs/heads/100", rc.getRef().getName());
577 			assertEquals(id(100), rc.getRef().getObjectId());
578 
579 			assertTrue("has master", rc.next());
580 			assertEquals("refs/heads/master", rc.getRef().getName());
581 			assertEquals(id(100), rc.getRef().getObjectId());
582 
583 			assertFalse(rc.next());
584 		}
585 	}
586 
587 	@SuppressWarnings("boxing")
588 	@Test
589 	public void byObjectIdOneRefWithIndex() throws IOException {
590 		List<Ref> refs = new ArrayList<>();
591 		for (int i = 1; i <= 5200; i++) {
592 			refs.add(ref(String.format("refs/heads/%02d", i), i));
593 		}
594 		refs.add(ref("refs/heads/master", 100));
595 
596 		ReftableReader t = read(write(refs));
597 		assertTrue(stats.objIndexSize() > 0);
598 
599 		try (RefCursor rc = t.byObjectId(id(42))) {
600 			assertTrue("has 42", rc.next());
601 			assertEquals("refs/heads/42", rc.getRef().getName());
602 			assertEquals(id(42), rc.getRef().getObjectId());
603 			assertFalse(rc.next());
604 		}
605 		try (RefCursor rc = t.byObjectId(id(100))) {
606 			assertTrue("has 100", rc.next());
607 			assertEquals("refs/heads/100", rc.getRef().getName());
608 			assertEquals(id(100), rc.getRef().getObjectId());
609 
610 			assertTrue("has master", rc.next());
611 			assertEquals("refs/heads/master", rc.getRef().getName());
612 			assertEquals(id(100), rc.getRef().getObjectId());
613 
614 			assertFalse(rc.next());
615 		}
616 	}
617 
618 	@Test
619 	public void unpeeledDoesNotWrite() {
620 		try {
621 			write(new ObjectIdRef.Unpeeled(PACKED, MASTER, id(1)));
622 			fail("expected IOException");
623 		} catch (IOException e) {
624 			assertEquals(JGitText.get().peeledRefIsRequired, e.getMessage());
625 		}
626 	}
627 
628 	@Test
629 	public void nameTooLongDoesNotWrite() throws IOException {
630 		try {
631 			ReftableConfig cfg = new ReftableConfig();
632 			cfg.setRefBlockSize(64);
633 
634 			ByteArrayOutputStream buffer = new ByteArrayOutputStream();
635 			ReftableWriter writer = new ReftableWriter(cfg).begin(buffer);
636 			writer.writeRef(ref("refs/heads/i-am-not-a-teapot", 1));
637 			writer.finish();
638 			fail("expected BlockSizeTooSmallException");
639 		} catch (BlockSizeTooSmallException e) {
640 			assertEquals(85, e.getMinimumBlockSize());
641 		}
642 	}
643 
644 	@Test
645 	public void badCrc32() throws IOException {
646 		byte[] table = write();
647 		table[table.length - 1] = 0x42;
648 
649 		try {
650 			read(table).seekRef(HEAD);
651 			fail("expected IOException");
652 		} catch (IOException e) {
653 			assertEquals(JGitText.get().invalidReftableCRC, e.getMessage());
654 		}
655 	}
656 
657 
658 	private static void assertScan(List<Ref> refs, Reftable t)
659 			throws IOException {
660 		try (RefCursor rc = t.allRefs()) {
661 			for (Ref exp : refs) {
662 				assertTrue("has " + exp.getName(), rc.next());
663 				Ref act = rc.getRef();
664 				assertEquals(exp.getName(), act.getName());
665 				assertEquals(exp.getObjectId(), act.getObjectId());
666 			}
667 			assertFalse(rc.next());
668 		}
669 	}
670 
671 	private static void assertSeek(List<Ref> refs, Reftable t)
672 			throws IOException {
673 		for (Ref exp : refs) {
674 			try (RefCursor rc = t.seekRef(exp.getName())) {
675 				assertTrue("has " + exp.getName(), rc.next());
676 				Ref act = rc.getRef();
677 				assertEquals(exp.getName(), act.getName());
678 				assertEquals(exp.getObjectId(), act.getObjectId());
679 				assertFalse(rc.next());
680 			}
681 		}
682 	}
683 
684 	private static Ref ref(String name, int id) {
685 		return new ObjectIdRef.PeeledNonTag(PACKED, name, id(id));
686 	}
687 
688 	private static Ref tag(String name, int id1, int id2) {
689 		return new ObjectIdRef.PeeledTag(PACKED, name, id(id1), id(id2));
690 	}
691 
692 	private static Ref sym(String name, String target) {
693 		return new SymbolicRef(name, newRef(target));
694 	}
695 
696 	private static Ref newRef(String name) {
697 		return new ObjectIdRef.Unpeeled(NEW, name, null);
698 	}
699 
700 	private static ObjectId id(int i) {
701 		byte[] buf = new byte[OBJECT_ID_LENGTH];
702 		buf[0] = (byte) (i & 0xff);
703 		buf[1] = (byte) ((i >>> 8) & 0xff);
704 		buf[2] = (byte) ((i >>> 16) & 0xff);
705 		buf[3] = (byte) (i >>> 24);
706 		return ObjectId.fromRaw(buf);
707 	}
708 
709 	private static ReftableReader read(byte[] table) {
710 		return new ReftableReader(BlockSource.from(table));
711 	}
712 
713 	private byte[] write(Ref... refs) throws IOException {
714 		return write(Arrays.asList(refs));
715 	}
716 
717 	private byte[] write(Collection<Ref> refs) throws IOException {
718 		ByteArrayOutputStream buffer = new ByteArrayOutputStream();
719 		stats = new ReftableWriter()
720 				.begin(buffer)
721 				.sortAndWriteRefs(refs)
722 				.finish()
723 				.getStats();
724 		return buffer.toByteArray();
725 	}
726 }