// Copyright 2009 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.enterprise.connector.util.diffing;
import junit.framework.TestCase;
import java.io.BufferedReader;
import java.io.FilterReader;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
/**
*/
public class SnapshotReaderTest extends TestCase {
private MockDocumentSnapshot good1;
private MockDocumentSnapshot good2;
@Override
public void setUp(){
good1 = new MockDocumentSnapshot("good1", "good1.extra");
good2 = new MockDocumentSnapshot("good1", "good2.extra");
}
public void testBasics() throws SnapshotReaderException, IOException {
BufferedReader br = mkReader(good1, good2);
SnapshotReader reader =
new SnapshotReader(br, "test", 7, new MockDocumentSnapshotFactory());
assertEquals(7, reader.getSnapshotNumber());
DocumentSnapshot read = reader.read();
assertNotNull(read);
assertEquals(read, good1);
read = reader.read();
assertNotNull(read);
assertEquals(read, good2);
read = reader.read();
assertNull(read);
}
public void testReaderBackwardCompatibility() throws IOException, SnapshotReaderException {
BufferedReader br = mkReaderWithJsonTypeSnapshot(good1, good2);
SnapshotReader reader =
new SnapshotReader(br, "test", 7, new MockDocumentSnapshotFactory());
assertEquals(7, reader.getSnapshotNumber());
DocumentSnapshot read = reader.read();
assertNotNull(read);
assertEquals(read, good1);
read = reader.read();
assertNotNull(read);
assertEquals(read, good2);
read = reader.read();
assertNull(read);
}
public void testMissingField() throws IOException {
try {
DocumentSnapshot bad = new MissingIdSnapshot("badId", "bad.extra");
BufferedReader br = mkReader(bad);
SnapshotReader reader = new SnapshotReader(br, "path", 9,
new MockDocumentSnapshotFactory());
reader.read();
fail();
} catch (SnapshotReaderException expected) {
assertTrue(expected.getCause().getMessage().contains(
MockDocumentSnapshot.Field.DOCUMENT_ID.toString()));
}
}
public void testBadReader() {
class FailingReader extends FilterReader {
FailingReader() {
super(new StringReader(""));
}
@Override
public int read(char[] buf, int offset, int len) throws IOException {
throw new IOException();
}
}
try {
SnapshotReader reader =
new SnapshotReader(new BufferedReader(new FailingReader()), "string",
4, new MockDocumentSnapshotFactory());
reader.read();
fail();
} catch (SnapshotReaderException expected) {
assertTrue(expected.getMessage().contains("failed to decide which record reader to use"));
}
}
/**
* Create a reader that contains {@code n} records. The last-modified time is
* the number of the record.
*
* @param n
* @return a mock reader.
* @throws SnapshotReaderException
*/
private SnapshotReader createMockInput(int n)
throws SnapshotReaderException, IOException {
List<MockDocumentSnapshot> snapshots = new ArrayList<MockDocumentSnapshot>();
for (int ix = 0; ix < n; ix++) {
snapshots.add(new MockDocumentSnapshot(Integer.toString(ix), "extra."+ix));
}
BufferedReader br = mkReader(snapshots.toArray(
new MockDocumentSnapshot[snapshots.size()]));
return new SnapshotReader(br, "testPath", 17,
new MockDocumentSnapshotFactory());
}
public void testGetLineNumber()
throws SnapshotReaderException, IOException {
SnapshotReader reader = createMockInput(100);
for (int k = 0; k < Integer.MAX_VALUE; ++k) {
assertEquals(k, reader.getRecordNumber());
DocumentSnapshot dss = reader.read();
assertEquals(k + 1, reader.getRecordNumber());
if (dss == null) {
break;
}
assertEquals(k, Integer.parseInt(dss.getDocumentId()));
}
}
public void testSkipRecords()
throws SnapshotReaderException, IOException, InterruptedException {
SnapshotReader reader = createMockInput(100);
reader.skipRecords(0);
DocumentSnapshot dss = reader.read();
assertEquals("0", dss.getDocumentId());
reader.skipRecords(7);
assertEquals("8", reader.read().getDocumentId());
try {
reader.skipRecords(1000);
fail("skipped too many records");
} catch (SnapshotReaderException e) {
assertTrue(e.getMessage().contains("snapshot contains only"));
}
}
public void testMissingLength() throws Exception {
String missingLength = SnapshotWriter.LENGTH_DELIMITER + "random padding";
BufferedReader br = new BufferedReader(new StringReader(missingLength));
SnapshotReader r = new SnapshotReader(br, "testPath", 17,
new MockDocumentSnapshotFactory());
try {
r.read();
fail();
} catch (SnapshotReaderException sne) {
assertTrue(sne.getMessage().contains(
"failed to read snapshot record with missing length"));
}
}
public void testOverflow() throws Exception {
String overflowingLength = "1" + Integer.MAX_VALUE
+ SnapshotWriter.LENGTH_DELIMITER + "random padding";
BufferedReader br = new BufferedReader(new StringReader(overflowingLength));
SnapshotReader r = new SnapshotReader(br, "testPath", 17,
new MockDocumentSnapshotFactory());
try {
r.read();
fail();
} catch (SnapshotReaderException sne) {
assertTrue(sne.getMessage().contains(
"failed to read snapshot record with invalid length"));
}
}
public void testInvalidLength() throws SnapshotReaderException {
String invalidLength = "max" + Integer.MAX_VALUE +
SnapshotWriter.LENGTH_DELIMITER + "random padding";
BufferedReader br = new BufferedReader(new StringReader(invalidLength));
SnapshotReader r = new SnapshotReader(br, "testPath", 17,
new MockDocumentSnapshotFactory());
try {
r.read();
fail();
} catch (SnapshotReaderException sne) {
assertTrue(sne.getMessage().contains(
"failed to read snapshot record with invalid length"));
}
}
public void testMissingLengthDelim() throws SnapshotStoreException {
String ssString = good1.toString();
String invalidLength = ssString.length() + ssString
+ SnapshotWriter.RECORD_DELIMITER;
BufferedReader br = new BufferedReader(new StringReader(invalidLength));
SnapshotReader r = new SnapshotReader(br, "testPath", 17,
new MockDocumentSnapshotFactory());
try {
r.read();
fail();
} catch (SnapshotReaderException sne) {
assertTrue(sne.getMessage().contains(
"failed to read snapshot record with missing length delimiter"));
}
}
public void testStringFormTooLong() throws SnapshotStoreException {
String ssString = good1.toString();
String invalidLength = "" + (ssString.length() - 1)
+ SnapshotWriter.LENGTH_DELIMITER
+ ssString
+ SnapshotWriter.RECORD_DELIMITER;
BufferedReader br = new BufferedReader(new StringReader(invalidLength));
SnapshotReader r = new SnapshotReader(br, "testPath", 17,
new MockDocumentSnapshotFactory());
try {
r.read();
fail();
} catch (SnapshotReaderException sne) {
assertTrue(sne.getMessage().contains(
"failed to read snapshot record missing record delimiter "));
}
}
public void testStringFormTooShort() throws SnapshotReaderException {
String ssString = good1.toString();
String invalidLength = "" + (ssString.length() + 2)
+ SnapshotWriter.LENGTH_DELIMITER
+ ssString
+ SnapshotWriter.RECORD_DELIMITER;
BufferedReader br = new BufferedReader(new StringReader(invalidLength));
SnapshotReader r = new SnapshotReader(br, "testPath", 17,
new MockDocumentSnapshotFactory());
try {
r.read();
fail();
} catch (SnapshotReaderException sne) {
assertTrue(sne.getMessage().contains(
"failed to read snapshot record with incomplete record "));
}
}
public void testMissingRecordDelim() throws SnapshotReaderException {
String ssString = good1.toString();
String noRecordDelim = "" + ssString.length()
+ SnapshotWriter.LENGTH_DELIMITER
+ ssString;
BufferedReader br = new BufferedReader(new StringReader(
noRecordDelim + noRecordDelim));
SnapshotReader r = new SnapshotReader(br, "testPath", 17,
new MockDocumentSnapshotFactory());
try {
r.read();
fail();
} catch (SnapshotReaderException sne) {
assertTrue(sne.getMessage().contains(
"failed to read snapshot record missing record delimiter "));
}
}
public void testNewLine() throws IOException, SnapshotReaderException{
DocumentSnapshot nl1 = new StringDocumentSnapshot("\nn\nl1\n");
DocumentSnapshot nl2 = new StringDocumentSnapshot("\n");
BufferedReader br = mkReader(nl1, nl2);
SnapshotReader reader =
new SnapshotReader(br, "test", 7, new StringDocumentSnapshotFactory());
assertEquals(7, reader.getSnapshotNumber());
DocumentSnapshot read = reader.read();
assertNotNull(read);
assertEquals(read.getDocumentId(), nl1.getDocumentId());
read = reader.read();
assertNotNull(read);
assertEquals(read.getDocumentId(), nl2.getDocumentId());
read = reader.read();
assertNull(read);
}
public void testSkipRecordsInterrupt()
throws SnapshotStoreException, IOException {
SnapshotReader reader = createMockInput(100);
try {
Thread.currentThread().interrupt();
reader.skipRecords(25);
fail();
} catch (InterruptedException ie) {
//Expected.
} finally {
assertFalse(Thread.interrupted());
}
DocumentSnapshot dss = reader.read();
assertEquals("0", dss.getDocumentId());
}
private BufferedReader mkReader(DocumentSnapshot... snapshots)
throws IOException {
Writer writer = new StringWriter();
for (DocumentSnapshot snapshot : snapshots) {
SnapshotWriter.write(snapshot, writer);
}
return new BufferedReader(new StringReader(writer.toString()));
}
/**
* Creates a snapshot representation of previous version snapshot files.
* @return BufferedReader
* @throws IOException
*/
private BufferedReader mkReaderWithJsonTypeSnapshot(DocumentSnapshot...snapshots) throws IOException {
Writer writer = new StringWriter();
for (DocumentSnapshot snapshot : snapshots) {
String stringForm = snapshot.toString();
if (stringForm == null) {
throw new IllegalArgumentException(
"DocumentSnapshot.toString returned null.");
}
writer.write(stringForm);
writer.write(SnapshotWriter.RECORD_DELIMITER);
}
writer.flush();
return new BufferedReader(new StringReader(writer.toString()));
}
public static class MissingIdSnapshot extends MockDocumentSnapshot {
MissingIdSnapshot(String documentId, String extra) {
super(documentId, extra);
}
@Override
public String toString() {
String correctResult = super.toString();
return correctResult.replace(
MockDocumentSnapshot.Field.DOCUMENT_ID.name(),
MockDocumentSnapshot.Field.DOCUMENT_ID.name() + "_not");
}
}
static class StringDocumentSnapshot implements DocumentSnapshot {
private final String documentId;
public StringDocumentSnapshot(String documentId) {
this.documentId = documentId;
}
public String getDocumentId() {
return documentId;
}
public DocumentHandle getUpdate(DocumentSnapshot onGsa) {
throw new UnsupportedOperationException();
}
@Override
public String toString() {
return documentId;
}
}
static class StringDocumentSnapshotFactory
implements DocumentSnapshotFactory {
public DocumentSnapshot fromString(String stringForm) {
return new StringDocumentSnapshot(stringForm);
}
}
}