/* * Copyright (C) 2010, Google Inc. * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available * under the terms of the Eclipse Distribution License v1.0 which * accompanies this distribution, is reproduced below, and is * available at http://www.eclipse.org/org/documents/edl-v10.php * * All rights reserved. * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * - Neither the name of the Eclipse Foundation, Inc. nor the * names of its contributors may be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.eclipse.jgit.notes; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.util.Iterator; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.util.RawParseUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; public class NoteMapTest extends RepositoryTestCase { private TestRepository<Repository> tr; private ObjectReader reader; private ObjectInserter inserter; @Override @Before public void setUp() throws Exception { super.setUp(); tr = new TestRepository<Repository>(db); reader = db.newObjectReader(); inserter = db.newObjectInserter(); } @Override @After public void tearDown() throws Exception { reader.close(); inserter.close(); super.tearDown(); } @Test public void testReadFlatTwoNotes() throws Exception { RevBlob a = tr.blob("a"); RevBlob b = tr.blob("b"); RevBlob data1 = tr.blob("data1"); RevBlob data2 = tr.blob("data2"); RevCommit r = tr.commit() // .add(a.name(), data1) // .add(b.name(), data2) // .create(); tr.parseBody(r); NoteMap map = NoteMap.read(reader, r); assertNotNull("have map", map); assertTrue("has note for a", map.contains(a)); assertTrue("has note for b", map.contains(b)); assertEquals(data1, map.get(a)); assertEquals(data2, map.get(b)); assertFalse("no note for data1", map.contains(data1)); assertNull("no note for data1", map.get(data1)); } @Test public void testReadFanout2_38() throws Exception { RevBlob a = tr.blob("a"); RevBlob b = tr.blob("b"); RevBlob data1 = tr.blob("data1"); RevBlob data2 = tr.blob("data2"); RevCommit r = tr.commit() // .add(fanout(2, a.name()), data1) // .add(fanout(2, b.name()), data2) // .create(); tr.parseBody(r); NoteMap map = NoteMap.read(reader, r); assertNotNull("have map", map); assertTrue("has note for a", map.contains(a)); assertTrue("has note for b", map.contains(b)); assertEquals(data1, map.get(a)); assertEquals(data2, map.get(b)); assertFalse("no note for data1", map.contains(data1)); assertNull("no note for data1", map.get(data1)); } @Test public void testReadFanout2_2_36() throws Exception { RevBlob a = tr.blob("a"); RevBlob b = tr.blob("b"); RevBlob data1 = tr.blob("data1"); RevBlob data2 = tr.blob("data2"); RevCommit r = tr.commit() // .add(fanout(4, a.name()), data1) // .add(fanout(4, b.name()), data2) // .create(); tr.parseBody(r); NoteMap map = NoteMap.read(reader, r); assertNotNull("have map", map); assertTrue("has note for a", map.contains(a)); assertTrue("has note for b", map.contains(b)); assertEquals(data1, map.get(a)); assertEquals(data2, map.get(b)); assertFalse("no note for data1", map.contains(data1)); assertNull("no note for data1", map.get(data1)); } @Test public void testReadFullyFannedOut() throws Exception { RevBlob a = tr.blob("a"); RevBlob b = tr.blob("b"); RevBlob data1 = tr.blob("data1"); RevBlob data2 = tr.blob("data2"); RevCommit r = tr.commit() // .add(fanout(38, a.name()), data1) // .add(fanout(38, b.name()), data2) // .create(); tr.parseBody(r); NoteMap map = NoteMap.read(reader, r); assertNotNull("have map", map); assertTrue("has note for a", map.contains(a)); assertTrue("has note for b", map.contains(b)); assertEquals(data1, map.get(a)); assertEquals(data2, map.get(b)); assertFalse("no note for data1", map.contains(data1)); assertNull("no note for data1", map.get(data1)); } @Test public void testGetCachedBytes() throws Exception { final String exp = "this is test data"; RevBlob a = tr.blob("a"); RevBlob data = tr.blob(exp); RevCommit r = tr.commit() // .add(a.name(), data) // .create(); tr.parseBody(r); NoteMap map = NoteMap.read(reader, r); byte[] act = map.getCachedBytes(a, exp.length() * 4); assertNotNull("has data for a", act); assertEquals(exp, RawParseUtils.decode(act)); } @Test public void testWriteUnchangedFlat() throws Exception { RevBlob a = tr.blob("a"); RevBlob b = tr.blob("b"); RevBlob data1 = tr.blob("data1"); RevBlob data2 = tr.blob("data2"); RevCommit r = tr.commit() // .add(a.name(), data1) // .add(b.name(), data2) // .add(".gitignore", "") // .add("zoo-animals.txt", "") // .create(); tr.parseBody(r); NoteMap map = NoteMap.read(reader, r); assertTrue("has note for a", map.contains(a)); assertTrue("has note for b", map.contains(b)); RevCommit n = commitNoteMap(map); assertNotSame("is new commit", r, n); assertSame("same tree", r.getTree(), n.getTree()); } @Test public void testWriteUnchangedFanout2_38() throws Exception { RevBlob a = tr.blob("a"); RevBlob b = tr.blob("b"); RevBlob data1 = tr.blob("data1"); RevBlob data2 = tr.blob("data2"); RevCommit r = tr.commit() // .add(fanout(2, a.name()), data1) // .add(fanout(2, b.name()), data2) // .add(".gitignore", "") // .add("zoo-animals.txt", "") // .create(); tr.parseBody(r); NoteMap map = NoteMap.read(reader, r); assertTrue("has note for a", map.contains(a)); assertTrue("has note for b", map.contains(b)); // This is a non-lazy map, so we'll be looking at the leaf buckets. RevCommit n = commitNoteMap(map); assertNotSame("is new commit", r, n); assertSame("same tree", r.getTree(), n.getTree()); // Use a lazy-map for the next round of the same test. map = NoteMap.read(reader, r); n = commitNoteMap(map); assertNotSame("is new commit", r, n); assertSame("same tree", r.getTree(), n.getTree()); } @Test public void testCreateFromEmpty() throws Exception { RevBlob a = tr.blob("a"); RevBlob b = tr.blob("b"); RevBlob data1 = tr.blob("data1"); RevBlob data2 = tr.blob("data2"); NoteMap map = NoteMap.newEmptyMap(); assertFalse("no a", map.contains(a)); assertFalse("no b", map.contains(b)); map.set(a, data1); map.set(b, data2); assertEquals(data1, map.get(a)); assertEquals(data2, map.get(b)); map.remove(a); map.remove(b); assertFalse("no a", map.contains(a)); assertFalse("no b", map.contains(b)); map.set(a, "data1", inserter); assertEquals(data1, map.get(a)); map.set(a, null, inserter); assertFalse("no a", map.contains(a)); } @Test public void testEditFlat() throws Exception { RevBlob a = tr.blob("a"); RevBlob b = tr.blob("b"); RevBlob data1 = tr.blob("data1"); RevBlob data2 = tr.blob("data2"); RevCommit r = tr.commit() // .add(a.name(), data1) // .add(b.name(), data2) // .add(".gitignore", "") // .add("zoo-animals.txt", b) // .create(); tr.parseBody(r); NoteMap map = NoteMap.read(reader, r); map.set(a, data2); map.set(b, null); map.set(data1, b); map.set(data2, null); assertEquals(data2, map.get(a)); assertEquals(b, map.get(data1)); assertFalse("no b", map.contains(b)); assertFalse("no data2", map.contains(data2)); MutableObjectId id = new MutableObjectId(); for (int p = 42; p > 0; p--) { id.setByte(1, p); map.set(id, data1); } for (int p = 42; p > 0; p--) { id.setByte(1, p); assertTrue("contains " + id, map.contains(id)); } RevCommit n = commitNoteMap(map); map = NoteMap.read(reader, n); assertEquals(data2, map.get(a)); assertEquals(b, map.get(data1)); assertFalse("no b", map.contains(b)); assertFalse("no data2", map.contains(data2)); assertEquals(b, TreeWalk .forPath(reader, "zoo-animals.txt", n.getTree()).getObjectId(0)); } @Test public void testEditFanout2_38() throws Exception { RevBlob a = tr.blob("a"); RevBlob b = tr.blob("b"); RevBlob data1 = tr.blob("data1"); RevBlob data2 = tr.blob("data2"); RevCommit r = tr.commit() // .add(fanout(2, a.name()), data1) // .add(fanout(2, b.name()), data2) // .add(".gitignore", "") // .add("zoo-animals.txt", b) // .create(); tr.parseBody(r); NoteMap map = NoteMap.read(reader, r); map.set(a, data2); map.set(b, null); map.set(data1, b); map.set(data2, null); assertEquals(data2, map.get(a)); assertEquals(b, map.get(data1)); assertFalse("no b", map.contains(b)); assertFalse("no data2", map.contains(data2)); RevCommit n = commitNoteMap(map); map.set(a, null); map.set(data1, null); assertFalse("no a", map.contains(a)); assertFalse("no data1", map.contains(data1)); map = NoteMap.read(reader, n); assertEquals(data2, map.get(a)); assertEquals(b, map.get(data1)); assertFalse("no b", map.contains(b)); assertFalse("no data2", map.contains(data2)); assertEquals(b, TreeWalk .forPath(reader, "zoo-animals.txt", n.getTree()).getObjectId(0)); } @Test public void testLeafSplitsWhenFull() throws Exception { RevBlob data1 = tr.blob("data1"); MutableObjectId idBuf = new MutableObjectId(); RevCommit r = tr.commit() // .add(data1.name(), data1) // .create(); tr.parseBody(r); NoteMap map = NoteMap.read(reader, r); for (int i = 0; i < 254; i++) { idBuf.setByte(Constants.OBJECT_ID_LENGTH - 1, i); map.set(idBuf, data1); } RevCommit n = commitNoteMap(map); try (TreeWalk tw = new TreeWalk(reader)) { tw.reset(n.getTree()); while (tw.next()) { assertFalse("no fan-out subtree", tw.isSubtree()); } } for (int i = 254; i < 256; i++) { idBuf.setByte(Constants.OBJECT_ID_LENGTH - 1, i); map.set(idBuf, data1); } idBuf.setByte(Constants.OBJECT_ID_LENGTH - 2, 1); map.set(idBuf, data1); n = commitNoteMap(map); // The 00 bucket is fully split. String path = fanout(38, idBuf.name()); try (TreeWalk tw = TreeWalk.forPath(reader, path, n.getTree())) { assertNotNull("has " + path, tw); } // The other bucket is not. path = fanout(2, data1.name()); try (TreeWalk tw = TreeWalk.forPath(reader, path, n.getTree())) { assertNotNull("has " + path, tw); } } @Test public void testRemoveDeletesTreeFanout2_38() throws Exception { RevBlob a = tr.blob("a"); RevBlob data1 = tr.blob("data1"); RevTree empty = tr.tree(); RevCommit r = tr.commit() // .add(fanout(2, a.name()), data1) // .create(); tr.parseBody(r); NoteMap map = NoteMap.read(reader, r); map.set(a, null); RevCommit n = commitNoteMap(map); assertEquals("empty tree", empty, n.getTree()); } @Test public void testIteratorEmptyMap() { Iterator<Note> it = NoteMap.newEmptyMap().iterator(); assertFalse(it.hasNext()); } @Test public void testIteratorFlatTree() throws Exception { RevBlob a = tr.blob("a"); RevBlob b = tr.blob("b"); RevBlob data1 = tr.blob("data1"); RevBlob data2 = tr.blob("data2"); RevBlob nonNote = tr.blob("non note"); RevCommit r = tr.commit() // .add(a.name(), data1) // .add(b.name(), data2) // .add("nonNote", nonNote) // .create(); tr.parseBody(r); Iterator it = NoteMap.read(reader, r).iterator(); assertEquals(2, count(it)); } @Test public void testIteratorFanoutTree2_38() throws Exception { RevBlob a = tr.blob("a"); RevBlob b = tr.blob("b"); RevBlob data1 = tr.blob("data1"); RevBlob data2 = tr.blob("data2"); RevBlob nonNote = tr.blob("non note"); RevCommit r = tr.commit() // .add(fanout(2, a.name()), data1) // .add(fanout(2, b.name()), data2) // .add("nonNote", nonNote) // .create(); tr.parseBody(r); Iterator it = NoteMap.read(reader, r).iterator(); assertEquals(2, count(it)); } @Test public void testIteratorFanoutTree2_2_36() throws Exception { RevBlob a = tr.blob("a"); RevBlob b = tr.blob("b"); RevBlob data1 = tr.blob("data1"); RevBlob data2 = tr.blob("data2"); RevBlob nonNote = tr.blob("non note"); RevCommit r = tr.commit() // .add(fanout(4, a.name()), data1) // .add(fanout(4, b.name()), data2) // .add("nonNote", nonNote) // .create(); tr.parseBody(r); Iterator it = NoteMap.read(reader, r).iterator(); assertEquals(2, count(it)); } @Test public void testIteratorFullyFannedOut() throws Exception { RevBlob a = tr.blob("a"); RevBlob b = tr.blob("b"); RevBlob data1 = tr.blob("data1"); RevBlob data2 = tr.blob("data2"); RevBlob nonNote = tr.blob("non note"); RevCommit r = tr.commit() // .add(fanout(38, a.name()), data1) // .add(fanout(38, b.name()), data2) // .add("nonNote", nonNote) // .create(); tr.parseBody(r); Iterator it = NoteMap.read(reader, r).iterator(); assertEquals(2, count(it)); } @Test public void testShorteningNoteRefName() throws Exception { String expectedShortName = "review"; String noteRefName = Constants.R_NOTES + expectedShortName; assertEquals(expectedShortName, NoteMap.shortenRefName(noteRefName)); String nonNoteRefName = Constants.R_HEADS + expectedShortName; assertEquals(nonNoteRefName, NoteMap.shortenRefName(nonNoteRefName)); } private RevCommit commitNoteMap(NoteMap map) throws IOException { tr.tick(600); CommitBuilder builder = new CommitBuilder(); builder.setTreeId(map.writeTree(inserter)); tr.setAuthorAndCommitter(builder); return tr.getRevWalk().parseCommit(inserter.insert(builder)); } private static String fanout(int prefix, String name) { StringBuilder r = new StringBuilder(); int i = 0; for (; i < prefix && i < name.length(); i += 2) { if (i != 0) r.append('/'); r.append(name.charAt(i + 0)); r.append(name.charAt(i + 1)); } if (i < name.length()) { if (i != 0) r.append('/'); r.append(name.substring(i)); } return r.toString(); } private static int count(Iterator it) { int c = 0; while (it.hasNext()) { c++; it.next(); } return c; } }