// Copyright 2010 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.instantiator;
import com.google.enterprise.connector.persist.MockPersistentStore;
import com.google.enterprise.connector.persist.PersistentStore;
import com.google.enterprise.connector.persist.StoreContext;
import com.google.enterprise.connector.scheduler.Schedule;
import junit.framework.TestCase;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/** Tests for {@link ChangeDetectorImpl}. */
// TODO: Change StoreContext to String (instance name).
// TODO: More tests, especially including stamps and corner cases like
// deleted and then re-added, etc.
public class ChangeDetectorTest extends TestCase {
private PersistentStore store;
private ExceptionalChangeListener listener;
private ChangeDetector detector;
private List<String> expectedChanges;
public ChangeDetectorTest() {
}
@Override
public void setUp() {
store = new MockPersistentStore();
listener = new ExceptionalChangeListener();
detector = new ChangeDetectorImpl(store, listener);
expectedChanges = new ArrayList<String>();
}
/** Adds an instance to the store and records the action. */
private void addConnector(String connectorName) {
store.storeConnectorConfiguration(
new StoreContext(connectorName, "testType"),
new Configuration("testType", Collections.<String, String>emptyMap(),
null));
expectedChanges.add(MockChangeListener.CONNECTOR_ADDED + connectorName);
}
/** Updates an instance to the store and records the action. */
private void updateConnector(String connectorName) {
StoreContext context = new StoreContext(connectorName, "testType");
assertNotNull(store.getConnectorConfiguration(context));
store.storeConnectorConfiguration(context, new Configuration(
"testType", Collections.<String, String>emptyMap(), null));
expectedChanges.add(MockChangeListener.CONFIGURATION_CHANGED
+ connectorName);
}
/** Deletes an instance from the store and records the action. */
private void removeConnector(String connectorName) {
store.removeConnectorConfiguration(
new StoreContext(connectorName, "testType"));
expectedChanges.add(MockChangeListener.CONNECTOR_REMOVED + connectorName);
}
/** Sets the checkpoint for a connector instance and records the action. */
private void setCheckpoint(String connectorName) {
store.storeConnectorState(new StoreContext(connectorName, "testType"), "");
expectedChanges.add(MockChangeListener.CHECKPOINT_CHANGED + connectorName);
}
/** Sets the Schedule for a connector instance and records the action. */
private void setSchedule(String connectorName) {
store.storeConnectorSchedule(new StoreContext(connectorName, "testType"),
new Schedule());
expectedChanges.add(MockChangeListener.SCHEDULE_CHANGED + connectorName);
}
/**
* Compares the contents of the two lists as multisets, that is,
* considering only the members and not the ordering. Neither of
* the lists should be empty.
*
* @param expected the expected value
* @param actual the actual value
*/
private void assertEqualsMultiSet(List<String> expected,
List<String> actual) {
assertFalse(expected.isEmpty());
assertFalse(actual.isEmpty());
// Copy, sort, and compare.
List<String> left = new ArrayList<String>(expected);
List<String> right = new ArrayList<String>(actual);
Collections.sort(left);
Collections.sort(right);
assertEquals(left, right);
}
/**
* Assertst that expected and actual lists each have no recorded changes.
*
* @param expected the expected value
* @param actual the actual value
*/
private void assertNoChanges(List<String> expected, List<String> actual) {
assertTrue(expected.isEmpty());
assertTrue(actual.isEmpty());
}
/** Basic test of adding and deleting connector instances. */
public void testAddAndRemove() {
addConnector("c1");
detector.detect();
assertEqualsMultiSet(expectedChanges, listener.getChanges());
addConnector("b2");
addConnector("b1");
addConnector("b3");
detector.detect();
assertEqualsMultiSet(expectedChanges, listener.getChanges());
expectedChanges.clear();
listener.clear();
addConnector("c2");
addConnector("a1");
removeConnector("b2");
detector.detect();
assertEqualsMultiSet(expectedChanges, listener.getChanges());
}
/** Basic test of adding, updating, and deleting connector instances. */
public void testAddUpdateRemove() {
addConnector("c1");
detector.detect();
assertEqualsMultiSet(expectedChanges, listener.getChanges());
updateConnector("c1");
detector.detect();
assertEqualsMultiSet(expectedChanges, listener.getChanges());
removeConnector("c1");
detector.detect();
assertEqualsMultiSet(expectedChanges, listener.getChanges());
}
/** Test of updating schedules and checkpoints. */
public void testCheckpointsAndSchedules() {
addConnector("c1");
addConnector("c2");
detector.detect();
assertEqualsMultiSet(expectedChanges, listener.getChanges());
setSchedule("c1");
setSchedule("c2");
setCheckpoint("c2");
detector.detect();
assertEqualsMultiSet(expectedChanges, listener.getChanges());
expectedChanges.clear();
listener.clear();
setSchedule("c1");
setCheckpoint("c2");
detector.detect();
assertEqualsMultiSet(expectedChanges, listener.getChanges());
}
/** Test detecting no changes. */
public void testNoChanges() {
addConnector("c1");
addConnector("c2");
detector.detect();
assertEqualsMultiSet(expectedChanges, listener.getChanges());
// With no additional changes, change detector should find nothing.
expectedChanges.clear();
listener.clear();
detector.detect();
assertNoChanges(expectedChanges, listener.getChanges());
// With no additional changes, change detector should still find nothing.
detector.detect();
assertNoChanges(expectedChanges, listener.getChanges());
// Finally change something.
updateConnector("c1");
addConnector("b3");
detector.detect();
assertEqualsMultiSet(expectedChanges, listener.getChanges());
}
/** Test retry connector instantiation if it fails. */
public void testRetryInstantiationOnAdd() {
addConnector("c1");
addConnector("c2");
detector.detect();
assertFalse(listener.getChanges().isEmpty());
assertEqualsMultiSet(expectedChanges, listener.getChanges());
// There should be no pending changes.
expectedChanges.clear();
listener.clear();
detector.detect();
assertNoChanges(expectedChanges, listener.getChanges());
// Force a new connector instantiation to fail.
listener.beBad = true;
addConnector("c3");
detector.detect();
assertEqualsMultiSet(expectedChanges, listener.getChanges());
// The ChangeDetector should retry this one.
listener.clear();
detector.detect();
assertEqualsMultiSet(expectedChanges, listener.getChanges());
// The ChangeDetector should retry this one, but this time let it succeed.
listener.beBad = false;
listener.clear();
detector.detect();
assertEqualsMultiSet(expectedChanges, listener.getChanges());
// There should be no pending changes.
expectedChanges.clear();
listener.clear();
detector.detect();
assertNoChanges(expectedChanges, listener.getChanges());
}
/** Test retry connector instantiation if update config fails. */
public void testRetryInstantiationOnUpdate() {
addConnector("c1");
addConnector("c2");
detector.detect();
assertEqualsMultiSet(expectedChanges, listener.getChanges());
// There should be no pending changes.
expectedChanges.clear();
listener.clear();
detector.detect();
assertNoChanges(expectedChanges, listener.getChanges());
// Force a updated connector instantiation to fail.
listener.beBad = true;
updateConnector("c1");
detector.detect();
assertEqualsMultiSet(expectedChanges, listener.getChanges());
// The ChangeDetector should retry this one.
listener.clear();
detector.detect();
assertEqualsMultiSet(expectedChanges, listener.getChanges());
// The ChangeDetector should retry this one, but this time let it succeed.
listener.beBad = false;
listener.clear();
detector.detect();
assertEqualsMultiSet(expectedChanges, listener.getChanges());
// There should be no pending changes.
expectedChanges.clear();
listener.clear();
detector.detect();
assertNoChanges(expectedChanges, listener.getChanges());
}
/**
* A ChangeListener that optionally throws InstantiatorException
* for configuration changes. Used to test instantiation retry.
*/
private class ExceptionalChangeListener extends MockChangeListener {
boolean beBad = false;
@Override
public void connectorAdded(String connectorName,
Configuration configuration) throws InstantiatorException {
super.connectorAdded(connectorName, configuration);
if (beBad) {
throw new InstantiatorException(connectorName);
}
}
@Override
public void connectorConfigurationChanged(String connectorName,
Configuration configuration) throws InstantiatorException {
super.connectorConfigurationChanged(connectorName, configuration);
if (beBad) {
throw new InstantiatorException(connectorName);
}
}
}
}