760N/A/*
760N/A * CDDL HEADER START
760N/A *
760N/A * The contents of this file are subject to the terms of the
760N/A * Common Development and Distribution License (the "License").
760N/A * You may not use this file except in compliance with the License.
760N/A *
760N/A * See LICENSE.txt included in this distribution for the specific
760N/A * language governing permissions and limitations under the License.
760N/A *
760N/A * When distributing Covered Code, include this CDDL HEADER in each
760N/A * file and include the License file at LICENSE.txt.
760N/A * If applicable, add the following below this CDDL HEADER, with the
760N/A * fields enclosed by brackets "[]" replaced with your own identifying
760N/A * information: Portions Copyright [yyyy] [name of copyright owner]
760N/A *
760N/A * CDDL HEADER END
760N/A */
760N/A
760N/A/*
969N/A * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
760N/A * Use is subject to license terms.
760N/A */
760N/A
760N/Apackage org.opensolaris.opengrok.history;
760N/A
760N/Aimport java.io.File;
1466N/Aimport java.lang.reflect.InvocationTargetException;
1466N/Aimport java.lang.reflect.Method;
788N/Aimport java.sql.Connection;
760N/Aimport java.sql.DriverManager;
1466N/Aimport java.sql.PreparedStatement;
1466N/Aimport java.sql.ResultSet;
760N/Aimport java.sql.SQLException;
788N/Aimport java.sql.Statement;
1466N/Aimport java.util.ArrayList;
778N/Aimport java.util.Arrays;
856N/Aimport java.util.Collections;
778N/Aimport java.util.Date;
760N/Aimport java.util.Iterator;
778N/Aimport java.util.LinkedList;
760N/Aimport java.util.List;
1466N/A
760N/Aimport junit.framework.Test;
760N/Aimport junit.framework.TestCase;
760N/Aimport junit.framework.TestSuite;
1466N/A
1466N/Aimport org.opensolaris.opengrok.configuration.RuntimeEnvironment;
778N/Aimport org.opensolaris.opengrok.util.Executor;
760N/Aimport org.opensolaris.opengrok.util.TestRepository;
760N/A
760N/A/**
760N/A * Unit tests for {@code JDBCHistoryCache}.
760N/A */
760N/Apublic class JDBCHistoryCacheTest extends TestCase {
760N/A
760N/A private TestRepository repositories;
760N/A private JDBCHistoryCache cache;
1466N/A private static Boolean hasDbtools;
760N/A
760N/A private static final String DERBY_EMBEDDED_DRIVER =
760N/A "org.apache.derby.jdbc.EmbeddedDriver";
760N/A
1466N/A @SuppressWarnings("javadoc")
760N/A public JDBCHistoryCacheTest(String name) {
760N/A super(name);
760N/A }
760N/A
760N/A /**
760N/A * Create a suite of tests to run. If the Derby classes are not present,
760N/A * skip this test.
760N/A *
760N/A * @return tests to run
760N/A */
760N/A public static Test suite() {
760N/A try {
760N/A Class.forName(DERBY_EMBEDDED_DRIVER);
760N/A return new TestSuite(JDBCHistoryCacheTest.class);
760N/A } catch (ClassNotFoundException e) {
760N/A return new TestSuite("JDBCHistoryCacheTest - empty (no derby.jar)");
760N/A }
760N/A }
760N/A
1466N/A @SuppressWarnings("unused")
1466N/A private void dumpDB(String prefix) {
1466N/A String file = "/tmp/opengrok:" + prefix;
1466N/A File f = new File(file + ".sql");
1466N/A if (hasDbtools == null || hasDbtools.booleanValue()) {
1466N/A Class<?> clazz = null;
1466N/A try {
1466N/A clazz = Class.forName("org.apache.derby.tools.dblook");
1466N/A Method main = clazz.getMethod("main", String[].class);
1466N/A if (f.exists()) {
1466N/A f.delete();
1466N/A }
1466N/A String[] args = new String[] {"-d", getURL(), "-o", f.toString()};
1466N/A main.invoke(clazz, (Object) args);
1466N/A System.out.println("Wrote " + f);
1466N/A } catch (ClassNotFoundException e) {
1466N/A // do nothing
1466N/A } catch (SecurityException e) {
1466N/A // ignore
1466N/A } catch (NoSuchMethodException e) {
1466N/A // ignore
1466N/A } catch (IllegalArgumentException e) {
1466N/A // ignore
1466N/A } catch (IllegalAccessException e) {
1466N/A // ignore
1466N/A } catch (InvocationTargetException e) {
1466N/A // ignore
1466N/A }
1466N/A hasDbtools = Boolean.valueOf(clazz != null);
1466N/A }
1466N/A Connection conn = null;
1466N/A Statement stmt = null;
1466N/A ResultSet rs = null;
1466N/A PreparedStatement ps = null;
1466N/A ArrayList<String> tables = new ArrayList<String>();
1466N/A String schema = "OPENGROK";
1466N/A try {
1466N/A conn = DriverManager.getConnection(getURL());
1466N/A stmt = conn.createStatement();
1466N/A stmt.executeUpdate("SET SCHEMA SYS");
1466N/A rs = stmt.executeQuery("SELECT T.TABLEID, T.TABLENAME, " +
1466N/A "S.SCHEMANAME FROM SYS.SYSTABLES T, SYS.SYSSCHEMAS S " +
1466N/A "WHERE T.TABLETYPE = 'T' AND T.SCHEMAID = S.SCHEMAID");
1466N/A while (rs.next()) {
1466N/A String tableName = rs.getString(2);
1466N/A String schemaName = rs.getString(3);
1466N/A if (schemaName.equals(schema)) {
1466N/A tables.add(tableName);
1466N/A }
1466N/A }
1466N/A ps = conn.prepareStatement("CALL SYSCS_UTIL.SYSCS_EXPORT_TABLE "
1466N/A + "(?,?,?,?,?,?)");
1466N/A for (String table : tables) {
1466N/A f = new File(file + "_" + table + ".dat");
1466N/A if (f.exists()) {
1466N/A f.delete();
1466N/A }
1466N/A ps.setString(1, schema); ps.setString(2, table);
1466N/A ps.setString(3, f.toString()); ps.setString(4, "|");
1466N/A ps.setString(5, null); ps.setString(6, null);
1466N/A ps.execute();
1466N/A System.out.println("Wrote " + f);
1466N/A }
1466N/A } catch (SQLException e) {
1466N/A System.out.println(e.getLocalizedMessage());
1466N/A } finally {
1466N/A try {
1466N/A if (stmt != null) stmt.close();
1466N/A } catch (SQLException e) {
1466N/A // ignore
1466N/A }
1466N/A try {
1466N/A if (ps != null) ps.close();
1466N/A } catch (SQLException e) {
1466N/A // ignore
1466N/A }
1466N/A try {
1466N/A if (conn != null) conn.close();
1466N/A } catch (SQLException e) {
1466N/A // ignore
1466N/A }
1466N/A }
1466N/A
1466N/A }
760N/A /**
760N/A * Set up the test environment with repositories and a cache instance.
760N/A */
760N/A @Override protected void setUp() throws Exception {
760N/A repositories = new TestRepository();
760N/A repositories.create(getClass().getResourceAsStream("repositories.zip"));
760N/A
760N/A cache = new JDBCHistoryCache(
760N/A DERBY_EMBEDDED_DRIVER, getURL() + ";create=true");
760N/A cache.initialize();
760N/A }
760N/A
760N/A /**
760N/A * Clean up after the test. Remove the test repositories and shut down
760N/A * the database.
760N/A */
760N/A @Override protected void tearDown() throws Exception {
760N/A repositories.destroy();
760N/A repositories = null;
760N/A
760N/A cache = null;
760N/A
760N/A try {
760N/A DriverManager.getConnection(getURL() + ";shutdown=true");
760N/A } catch (SQLException sqle) {
760N/A // Expect SQLException with SQLState 08006 on successful shutdown
760N/A if (!sqle.getSQLState().equals("08006")) {
760N/A throw sqle;
760N/A }
760N/A }
760N/A }
760N/A
760N/A /**
760N/A * Create a database URL to use for this test. The URL points to an
760N/A * in-memory Derby database.
760N/A *
760N/A * @return a database URL
760N/A */
760N/A private String getURL() {
760N/A return "jdbc:derby:memory:DB-" + getName();
760N/A }
760N/A
760N/A /**
778N/A * Import a new changeset into a Mercurial repository.
778N/A *
778N/A * @param reposRoot the root of the repository
778N/A * @param changesetFile file that contains the changeset to import
778N/A */
1466N/A private static void importHgChangeset(File reposRoot, String changesetFile) {
778N/A String[] cmdargs = {
1182N/A MercurialRepository.CMD_FALLBACK, "import", changesetFile
778N/A };
778N/A Executor exec = new Executor(Arrays.asList(cmdargs), reposRoot);
778N/A int exitCode = exec.exec();
778N/A if (exitCode != 0) {
778N/A fail("hg import failed." +
778N/A "\nexit code: " + exitCode +
778N/A "\nstdout:\n" + exec.getOutputString() +
778N/A "\nstderr:\n" + exec.getErrorString());
778N/A }
778N/A }
778N/A
778N/A /**
778N/A * Assert that two HistoryEntry objects are equal.
778N/A * @param expected the expected entry
778N/A * @param actual the actual entry
778N/A * @throws AssertFailure if the two entries don't match
778N/A */
1466N/A private static void assertSameEntries(
778N/A List<HistoryEntry> expected, List<HistoryEntry> actual) {
778N/A assertEquals("Unexpected size", expected.size(), actual.size());
778N/A Iterator<HistoryEntry> actualIt = actual.iterator();
778N/A for (HistoryEntry expectedEntry : expected) {
778N/A assertSameEntry(expectedEntry, actualIt.next());
778N/A }
778N/A assertFalse("More entries than expected", actualIt.hasNext());
778N/A }
778N/A
778N/A /**
778N/A * Assert that two lists of HistoryEntry objects are equal.
778N/A * @param expected the expected list of entries
778N/A * @param actual the actual list of entries
778N/A * @throws AssertFailure if the two lists don't match
778N/A */
1466N/A private static void assertSameEntry(HistoryEntry expected, HistoryEntry actual) {
778N/A assertEquals(expected.getAuthor(), actual.getAuthor());
778N/A assertEquals(expected.getRevision(), actual.getRevision());
778N/A assertEquals(expected.getDate(), actual.getDate());
778N/A assertEquals(expected.getMessage(), actual.getMessage());
802N/A assertEquals(expected.getFiles(), actual.getFiles());
778N/A }
778N/A
778N/A /**
760N/A * Basic tests for the {@code store()} and {@code get()} methods.
1466N/A * @throws Exception
760N/A */
1466N/A @SuppressWarnings("boxing")
760N/A public void testStoreAndGet() throws Exception {
760N/A File reposRoot = new File(repositories.getSourceRoot(), "mercurial");
760N/A Repository repos = RepositoryFactory.getRepository(reposRoot);
760N/A
768N/A History historyToStore = repos.getHistory(reposRoot);
760N/A
760N/A cache.store(historyToStore, repos);
795N/A cache.optimize();
760N/A
776N/A // test get history for single file
776N/A
763N/A File makefile = new File(reposRoot, "Makefile");
763N/A assertTrue(makefile.exists());
760N/A
1466N/A History retrievedHistory = cache.get(makefile, repos, true, null);
1466N/A History retrievedHistory2 = cache.get(makefile, repos, true, Boolean.FALSE);
1466N/A assertSameEntries(retrievedHistory.getHistoryEntries(),
1466N/A retrievedHistory2.getHistoryEntries());
1466N/A retrievedHistory2 = cache.get(makefile, repos, true, Boolean.TRUE);
1466N/A assertTrue("entry set should be empty",
1466N/A retrievedHistory2.getHistoryEntries().isEmpty());
760N/A
760N/A List<HistoryEntry> entries = retrievedHistory.getHistoryEntries();
760N/A
760N/A assertEquals("Unexpected number of entries", 2, entries.size());
760N/A
760N/A final String TROND = "Trond Norbye <trond.norbye@sun.com>";
760N/A
760N/A Iterator<HistoryEntry> entryIt = entries.iterator();
760N/A
760N/A HistoryEntry e1 = entryIt.next();
760N/A assertEquals(TROND, e1.getAuthor());
760N/A assertEquals("2:585a1b3f2efb", e1.getRevision());
775N/A assertEquals(2, e1.getFiles().size());
760N/A
760N/A HistoryEntry e2 = entryIt.next();
760N/A assertEquals(TROND, e2.getAuthor());
760N/A assertEquals("1:f24a5fd7a85d", e2.getRevision());
775N/A assertEquals(3, e2.getFiles().size());
776N/A
776N/A assertFalse(entryIt.hasNext());
776N/A
776N/A // test get history for directory
776N/A
1466N/A History dirHistory = cache.get(reposRoot, repos, true, null);
1466N/A assertSameEntries(historyToStore.getHistoryEntries(),
1466N/A dirHistory.getHistoryEntries());
1466N/A retrievedHistory2 = cache.get(reposRoot, repos, true, Boolean.TRUE);
1466N/A assertSameEntries(historyToStore.getHistoryEntries(),
1466N/A retrievedHistory2.getHistoryEntries());
1466N/A retrievedHistory2 = cache.get(reposRoot, repos, true, Boolean.FALSE);
1466N/A assertTrue("entry set should be empty" ,
1466N/A retrievedHistory2.getHistoryEntries().isEmpty());
778N/A
778N/A // test incremental update
778N/A
778N/A importHgChangeset(
778N/A reposRoot, getClass().getResource("hg-export.txt").getPath());
778N/A
778N/A repos.createCache(cache, cache.getLatestCachedRevision(repos));
795N/A cache.optimize();
778N/A
1466N/A History updatedHistory = cache.get(reposRoot, repos, true, null);
1466N/A long time = 1245446973L; // whole seconds only
1466N/A if (RuntimeEnvironment.isOldJVM()) {
1466N/A time /= 60; // whole minutes only
1466N/A time *= 60;
1466N/A }
778N/A HistoryEntry newEntry = new HistoryEntry(
1481N/A "3:78649c3ec6cb", null, new Date(time * 1000),
778N/A "xyz", "Return failure when executed with no arguments", true);
778N/A newEntry.addFile("/mercurial/main.c");
778N/A
778N/A LinkedList<HistoryEntry> updatedEntries = new LinkedList<HistoryEntry>(
778N/A updatedHistory.getHistoryEntries());
778N/A assertSameEntry(newEntry, updatedEntries.removeFirst());
778N/A assertSameEntries(historyToStore.getHistoryEntries(), updatedEntries);
1466N/A
1466N/A // prepare test for non-existing, i.e. removed file/directory using
1466N/A // isDir = {null, true, false}
1466N/A newEntry.addFile("/mercurial/subdir/bla.c");
1466N/A newEntry.setRevision("4:78649c3ec6cb");
1466N/A updatedEntries.clear();
1466N/A updatedEntries.add(newEntry);
1466N/A dirHistory.setHistoryEntries(updatedEntries);
1466N/A //dumpDB("0pre");
1466N/A cache.store(dirHistory, repos);
1466N/A //dumpDB("0post");
1466N/A File dir = new File(repos.getDirectoryName(), "subdir");
1466N/A assertTrue(cache.hasCacheForDirectory(dir, repos));
1466N/A File file = new File(dir, "bla.c");
1466N/A assertFalse(cache.hasCacheForDirectory(file, repos)); // not a dir
1466N/A // null == auto -> test for physical path -> fails -> assumes path is file
1466N/A assertTrue(cache.get(dir, repos, false, null).getHistoryEntries().isEmpty());
1466N/A assertFalse(cache.get(file, repos, false, null).getHistoryEntries().isEmpty());
1466N/A // force to take as file (same results as for null)
1466N/A assertTrue(cache.get(dir, repos, false, false).getHistoryEntries().isEmpty());
1466N/A assertFalse(cache.get(file, repos, false, false).getHistoryEntries().isEmpty());
1466N/A // force to take as dir
1466N/A assertFalse(cache.get(dir, repos, false, true).getHistoryEntries().isEmpty());
1466N/A assertTrue(cache.get(file, repos, false, true).getHistoryEntries().isEmpty());
969N/A
969N/A // test clearing of cache
969N/A cache.clear(repos);
1466N/A History clearedHistory = cache.get(reposRoot, repos, true, null);
969N/A assertTrue("History should be empty",
969N/A clearedHistory.getHistoryEntries().isEmpty());
969N/A cache.store(historyToStore, repos);
969N/A assertSameEntries(historyToStore.getHistoryEntries(),
1466N/A cache.get(reposRoot, repos, true, null).getHistoryEntries());
760N/A }
760N/A
761N/A /**
761N/A * Test that {@code getLatestCachedRevision()} returns the correct
761N/A * revision.
1466N/A * @throws Exception
761N/A */
761N/A public void testGetLatestCachedRevision() throws Exception {
761N/A File reposRoot = new File(repositories.getSourceRoot(), "mercurial");
761N/A Repository repos = RepositoryFactory.getRepository(reposRoot);
768N/A History history = repos.getHistory(reposRoot);
761N/A cache.store(history, repos);
795N/A cache.optimize();
761N/A
761N/A List<HistoryEntry> entries = history.getHistoryEntries();
761N/A HistoryEntry oldestEntry = entries.get(entries.size() - 1);
761N/A HistoryEntry mostRecentEntry = entries.get(0);
761N/A
761N/A assertTrue("Unexpected order of history entries",
761N/A oldestEntry.getDate().before(mostRecentEntry.getDate()));
761N/A
761N/A String latestRevision = mostRecentEntry.getRevision();
761N/A assertNotNull("Unknown latest revision", latestRevision);
761N/A assertEquals("Incorrect latest revision",
761N/A latestRevision, cache.getLatestCachedRevision(repos));
778N/A
778N/A // test incremental update
778N/A
778N/A importHgChangeset(
778N/A reposRoot, getClass().getResource("hg-export.txt").getPath());
778N/A repos.createCache(cache, latestRevision);
778N/A assertEquals("3:78649c3ec6cb", cache.getLatestCachedRevision(repos));
761N/A }
782N/A
782N/A /**
782N/A * Test that {@code hasCacheForDirectory()} works.
1466N/A * @throws Exception
782N/A */
782N/A public void testHasCacheForDirectory() throws Exception {
786N/A // Use a Mercurial repository and a Subversion repository in this test.
786N/A File hgRoot = new File(repositories.getSourceRoot(), "mercurial");
786N/A Repository hgRepos = RepositoryFactory.getRepository(hgRoot);
786N/A File svnRoot = new File(repositories.getSourceRoot(), "svn");
786N/A Repository svnRepos = RepositoryFactory.getRepository(svnRoot);
786N/A
786N/A // None of the repositories should have any history.
786N/A assertFalse(cache.hasCacheForDirectory(hgRoot, hgRepos));
786N/A assertFalse(cache.hasCacheForDirectory(svnRoot, svnRepos));
786N/A
786N/A // Store empty history, so still expect false.
786N/A cache.store(new History(), hgRepos);
786N/A cache.store(new History(), svnRepos);
786N/A assertFalse(cache.hasCacheForDirectory(hgRoot, hgRepos));
786N/A assertFalse(cache.hasCacheForDirectory(svnRoot, svnRepos));
786N/A
786N/A // Store history for Mercurial repository.
786N/A cache.store(hgRepos.getHistory(hgRoot), hgRepos);
786N/A assertTrue(cache.hasCacheForDirectory(hgRoot, hgRepos));
786N/A assertFalse(cache.hasCacheForDirectory(svnRoot, svnRepos));
786N/A
786N/A // Store history for Subversion repository.
1466N/A //dumpDB("pre");
786N/A cache.store(hgRepos.getHistory(svnRoot), svnRepos);
1466N/A //dumpDB("post");
786N/A assertTrue(cache.hasCacheForDirectory(hgRoot, hgRepos));
1466N/A // w/o mercurial repo fix there is a /mercurial subdir, but no /svn subdir
1466N/A // w/ mercurial repo fix there is still no subdir
1466N/A assertFalse(cache.hasCacheForDirectory(svnRoot, svnRepos));
782N/A }
788N/A
788N/A /**
788N/A * Test that get() is able to continue and return successfully after a lock
788N/A * timeout when accessing the database.
1466N/A * @throws Exception
788N/A */
788N/A public void testRetryGetOnTimeout() throws Exception {
788N/A File reposRoot = new File(repositories.getSourceRoot(), "mercurial");
788N/A Repository repos = RepositoryFactory.getRepository(reposRoot);
788N/A History history = repos.getHistory(reposRoot);
788N/A cache.store(history, repos);
788N/A
788N/A // Set the lock timeout to one second to make it go faster.
788N/A final Connection c = DriverManager.getConnection(getURL());
788N/A Statement s = c.createStatement();
788N/A s.execute("call syscs_util.syscs_set_database_property" +
788N/A "('derby.locks.waitTimeout', '1')");
788N/A
788N/A // Lock one of the tables exclusively in order to block get().
788N/A c.setAutoCommit(false);
808N/A // Originally, we locked the FILECHANGES table here, but that triggered
808N/A // a Derby bug (https://issues.apache.org/jira/browse/DERBY-4330), so
808N/A // now we lock the AUTHORS table instead.
899N/A s.execute("lock table opengrok.authors in exclusive mode");
788N/A s.close();
788N/A
788N/A // Roll back the transaction in 1.5 seconds so that get() is able to
788N/A // continue after the first timeout.
788N/A final Exception[] ex = new Exception[1];
788N/A Thread t = new Thread() {
788N/A @Override
788N/A public void run() {
788N/A try {
788N/A Thread.sleep(1500);
788N/A c.rollback();
788N/A c.close();
788N/A } catch (Exception e) {
788N/A ex[0] = e;
788N/A }
788N/A }
788N/A };
788N/A
788N/A t.start();
788N/A
788N/A // get() should be able to continue after a timeout.
788N/A assertSameEntries(
788N/A history.getHistoryEntries(),
1466N/A cache.get(reposRoot, repos, true, null).getHistoryEntries());
788N/A
788N/A t.join();
788N/A
788N/A // Expose any exception thrown in the helper thread.
788N/A if (ex[0] != null) {
788N/A throw ex[0];
788N/A }
788N/A }
856N/A
856N/A /**
856N/A * Regression test for bug #11663. If the commit message was longer than
856N/A * the maximum VARCHAR length, a truncation error would be raised by the
856N/A * database. Now the message should be truncated if such a message is
856N/A * encountered.
1466N/A * @throws Exception
856N/A */
856N/A public void testVeryLongCommitMessage() throws Exception {
856N/A File reposRoot = new File(repositories.getSourceRoot(), "mercurial");
856N/A Repository r = RepositoryFactory.getRepository(reposRoot);
856N/A HistoryEntry e0 = new HistoryEntry();
856N/A e0.setAuthor("dummy");
856N/A e0.setDate(new Date());
856N/A e0.addFile("/mercurial/readme.txt");
856N/A e0.setRevision("12:abcdef123456");
856N/A for (int msgLength = 0; msgLength < 40000; msgLength += 48) {
856N/A e0.appendMessage(
856N/A "this is a line with 48 chars, including newline");
856N/A }
856N/A
856N/A // Used to get a truncation error from the database here.
856N/A cache.store(new History(Collections.singletonList(e0)), r);
856N/A
1466N/A History h = cache.get(reposRoot, r, false, null);
856N/A assertEquals("One entry expected", 1, h.getHistoryEntries().size());
856N/A HistoryEntry e1 = h.getHistoryEntries().get(0);
856N/A assertEquals("Author", e0.getAuthor(), e1.getAuthor());
856N/A assertEquals("Date", e0.getDate(), e1.getDate());
856N/A assertEquals("Revision", e0.getRevision(), e1.getRevision());
856N/A assertTrue("No file list requested", e1.getFiles().isEmpty());
856N/A assertFalse("Long message should be truncated",
856N/A e0.getMessage().equals(e1.getMessage()));
856N/A assertEquals("Start of message should be equal to original",
856N/A e0.getMessage().substring(0, 1000),
856N/A e1.getMessage().substring(0, 1000));
856N/A }
864N/A
1466N/A @SuppressWarnings("javadoc")
1466N/A public void testGetInfo() {
864N/A String info = cache.getInfo();
864N/A assertTrue("Info should contain name of history cache",
864N/A info.startsWith("JDBCHistoryCache"));
864N/A assertTrue("Info should contain driver class",
864N/A info.contains(DERBY_EMBEDDED_DRIVER));
864N/A }
883N/A
883N/A /**
883N/A * Test that it is possible to store an entry with no author.
883N/A * Bug #11662.
1466N/A * @throws Exception
883N/A */
883N/A public void testNullAuthor() throws Exception {
883N/A File reposRoot = new File(repositories.getSourceRoot(), "svn");
883N/A Repository r = RepositoryFactory.getRepository(reposRoot);
883N/A // Create an entry where author is null
883N/A HistoryEntry e = new HistoryEntry(
1481N/A "1", null, new Date(), null, "Initial revision", true);
883N/A e.addFile("/svn/file.txt");
883N/A List<HistoryEntry> entries = Collections.singletonList(e);
883N/A cache.store(new History(entries), r);
883N/A assertSameEntries(
883N/A entries,
1466N/A cache.get(reposRoot, r, true, null).getHistoryEntries());
883N/A }
760N/A}