Separate concerns: provide usage data vs publish usage data
Let the data providers just provide data and have the data publisher
deal with publishing the data. This greatly improves the testability.
Change-Id: If2da4b53979f97ead78821ef259327afbba208a5
Signed-off-by: Adrian Görler <adrian.goerler@sap.com>
diff --git a/BUCK b/BUCK
index 2cb7dbe..d16b966 100644
--- a/BUCK
+++ b/BUCK
@@ -27,6 +27,9 @@
deps = GERRIT_PLUGIN_API + [
':quota__plugin',
'//lib:junit',
+ '//lib/easymock:easymock',
+ '//lib/log:log4j',
+ '//lib/log:impl_log4j',
],
source_under_test = [':quota__plugin'],
)
diff --git a/lib/BUCK b/lib/BUCK
index dc55495..6959cde 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -4,6 +4,7 @@
name = 'junit',
id = 'junit:junit:4.11',
sha1 = '4e031bb61df09069aeb2bffb4019e7a5034a4ee0',
+ attach_source = False,
license = 'DO_NOT_DISTRIBUTE',
deps = [':hamcrest-core'],
)
@@ -12,6 +13,7 @@
name = 'hamcrest-core',
id = 'org.hamcrest:hamcrest-core:1.3',
sha1 = '42a25dc3219429f0e5d060061f71acb49bf010a0',
+ attach_source = False,
license = 'DO_NOT_DISTRIBUTE',
visibility = ['//lib:junit'],
)
diff --git a/lib/commons/BUCK b/lib/commons/BUCK
index 43ba8aa..be9dfe5 100644
--- a/lib/commons/BUCK
+++ b/lib/commons/BUCK
@@ -5,6 +5,7 @@
id = 'commons-lang:commons-lang:2.5',
sha1 = 'b0236b252e86419eef20c31a44579d2aee2f0a69',
exclude = ['META-INF/LICENSE.txt', 'META-INF/NOTICE.txt'],
+ attach_source = False,
license = 'Apache2.0',
)
diff --git a/lib/easymock/BUCK b/lib/easymock/BUCK
new file mode 100644
index 0000000..3cf45ae
--- /dev/null
+++ b/lib/easymock/BUCK
@@ -0,0 +1,31 @@
+include_defs('//bucklets/maven_jar.bucklet')
+
+maven_jar(
+ name = 'easymock',
+ id = 'org.easymock:easymock:3.2',
+ sha1 = '00c82f7fa3ef377d8954b1db25123944b5af2ba4',
+ license = 'DO_NOT_DISTRIBUTE',
+ attach_source = False,
+ deps = [
+ ':cglib-2_2',
+ ':objenesis',
+ ],
+)
+
+maven_jar(
+ name = 'cglib-2_2',
+ id = 'cglib:cglib-nodep:2.2.2',
+ sha1 = '00d456bb230c70c0b95c76fb28e429d42f275941',
+ license = 'DO_NOT_DISTRIBUTE',
+ attach_source = False,
+)
+
+maven_jar(
+ name = 'objenesis',
+ id = 'org.objenesis:objenesis:1.2',
+ sha1 = 'bfcb0539a071a4c5a30690388903ac48c0667f2a',
+ license = 'DO_NOT_DISTRIBUTE',
+ visibility = ['//lib/powermock:powermock-reflect'],
+ attach_source = False,
+)
+
diff --git a/pom.xml b/pom.xml
index d0072e7..31cbbaa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -80,6 +80,12 @@
<version>4.11</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.easymock</groupId>
+ <artifactId>easymock</artifactId>
+ <version>3.2</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<repositories>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/quota/FetchAndPushEventCreator.java b/src/main/java/com/googlesource/gerrit/plugins/quota/FetchAndPushEventCreator.java
new file mode 100644
index 0000000..fd41c4b
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/quota/FetchAndPushEventCreator.java
@@ -0,0 +1,60 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// 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.googlesource.gerrit.plugins.quota;
+
+import com.google.gerrit.extensions.events.UsageDataPublishedListener.Event;
+import com.google.gerrit.extensions.events.UsageDataPublishedListener.MetaData;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.inject.Singleton;
+
+@Singleton
+public class FetchAndPushEventCreator implements UsageDataEventCreator {
+
+ static final MetaData PUSH_COUNT = new MetaDataImpl("pushCount", "", "",
+ "number of pushes to the repository since the last event");
+
+ static final MetaData FETCH_COUNT = new MetaDataImpl("fetchCount", "", "",
+ "number of fetches from the repository since the last event");
+
+ private final ProjectCache projectCache;
+ private final PersistentCounter counts;
+ private final MetaData metaData;
+
+ public FetchAndPushEventCreator(ProjectCache projectCache, PersistentCounter counts,
+ MetaData metaData) {
+ this.projectCache = projectCache;
+ this.counts = counts;
+ this.metaData = metaData;
+ }
+
+ @Override
+ public String getName() {
+ return metaData.getName();
+ }
+
+ @Override
+ public Event create() {
+ UsageDataEvent event = new UsageDataEvent(metaData);
+ for (Project.NameKey p : projectCache.all()) {
+ long currentCount = counts.getAndReset(p);
+ if (currentCount != 0) {
+ event.addData(currentCount, p.get());
+ }
+ }
+ return event;
+ }
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/quota/Module.java b/src/main/java/com/googlesource/gerrit/plugins/quota/Module.java
index 6fbe150..1b0867a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/quota/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/quota/Module.java
@@ -28,7 +28,6 @@
import org.eclipse.jgit.transport.PostReceiveHook;
import org.eclipse.jgit.transport.PreUploadHook;
-
class Module extends AbstractModule {
@Override
@@ -43,6 +42,7 @@
.to(FetchAndPushListener.class);
DynamicSet.bind(binder(), PreUploadHook.class)
.to(FetchAndPushListener.class);
+ DynamicSet.setOf(binder(), UsageDataEventCreator.class);
install(MaxRepositorySizeQuota.module());
install(PersistentCounter.module());
install(new RestApiModule() {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/quota/PersistentCounter.java b/src/main/java/com/googlesource/gerrit/plugins/quota/PersistentCounter.java
index d99bcb5..b3c22df 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/quota/PersistentCounter.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/quota/PersistentCounter.java
@@ -2,12 +2,17 @@
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
+import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.cache.CacheModule;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provides;
import com.google.inject.Singleton;
+import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
+import com.google.inject.name.Names;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
@@ -23,6 +28,16 @@
Loader.class).expireAfterWrite(Integer.MAX_VALUE, TimeUnit.DAYS);
persist(PUSH, Project.NameKey.class, AtomicLong.class).loader(
Loader.class).expireAfterWrite(Integer.MAX_VALUE, TimeUnit.DAYS);
+ DynamicSet.bind(binder(), UsageDataEventCreator.class).to(creatorKey(FETCH));
+ DynamicSet.bind(binder(), UsageDataEventCreator.class).to(creatorKey(PUSH));
+ DynamicSet.bind(binder(), UsageDataEventCreator.class).to(RepoSizeEventCreator.class);
+ }
+
+ private Key<UsageDataEventCreator> creatorKey(String kind) {
+ Key<UsageDataEventCreator> pushCreatorKey =
+ Key.get(new TypeLiteral<UsageDataEventCreator>() {},
+ Names.named(kind));
+ return pushCreatorKey;
}
@Provides @Singleton @Named(FETCH)
@@ -36,6 +51,18 @@
@Named(PUSH) LoadingCache<Project.NameKey, AtomicLong> counts) {
return new PersistentCounter(counts);
}
+
+ @Provides @Singleton @Named(FETCH)
+ UsageDataEventCreator provideFetchEventCreator(ProjectCache projectCache,
+ @Named(FETCH) PersistentCounter counts) {
+ return new FetchAndPushEventCreator(projectCache, counts, FetchAndPushEventCreator.FETCH_COUNT);
+ }
+
+ @Provides @Singleton @Named(PUSH)
+ UsageDataEventCreator providePushEventCreator(ProjectCache projectCache,
+ @Named(PUSH) PersistentCounter counts) {
+ return new FetchAndPushEventCreator(projectCache, counts, FetchAndPushEventCreator.PUSH_COUNT);
+ }
};
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/quota/Publisher.java b/src/main/java/com/googlesource/gerrit/plugins/quota/Publisher.java
index 26637f8..f44c92a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/quota/Publisher.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/quota/Publisher.java
@@ -15,20 +15,14 @@
package com.googlesource.gerrit.plugins.quota;
import com.google.gerrit.extensions.events.UsageDataPublishedListener;
-import com.google.gerrit.extensions.events.UsageDataPublishedListener.Data;
import com.google.gerrit.extensions.events.UsageDataPublishedListener.Event;
-import com.google.gerrit.extensions.events.UsageDataPublishedListener.MetaData;
import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Inject;
import com.google.inject.Singleton;
-import com.google.inject.name.Named;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
@@ -36,33 +30,16 @@
public class Publisher implements Runnable {
private static final Logger log = LoggerFactory.getLogger(Publisher.class);
- private static final MetaData REPO_SIZE = new MetaDataImpl("repoSize",
- "total file size of the repository", "byte", "B");
-
- private static final MetaData PUSH_COUNT = new MetaDataImpl("pushCount",
- "number of pushes to the repository since the last event", "", "");
-
- private static final MetaData FETCH_COUNT = new MetaDataImpl("fetchCount",
- "number of fetches from the repository since the last event", "", "");
private final Iterable<UsageDataPublishedListener> listeners;
- private final ProjectCache projectCache;
- private final RepoSizeCache repoSizeCache;
- private final PersistentCounter fetchCounts;
- private final PersistentCounter pushCounts;
+ private final DynamicSet<UsageDataEventCreator> creators;
@Inject
public Publisher(
DynamicSet<UsageDataPublishedListener> listeners,
- ProjectCache projectCache,
- RepoSizeCache repoSizeCache,
- @Named(PersistentCounter.FETCH) PersistentCounter fetchCounts,
- @Named(PersistentCounter.PUSH) PersistentCounter pushCounts) {
+ DynamicSet<UsageDataEventCreator> creators) {
this.listeners = listeners;
- this.projectCache = projectCache;
- this.repoSizeCache = repoSizeCache;
- this.fetchCounts = fetchCounts;
- this.pushCounts = pushCounts;
+ this.creators = creators;
}
@Override
@@ -71,84 +48,25 @@
return;
}
- UsageDataEvent repoSizeEvent = createRepoSizeEvent();
- UsageDataEvent fetchCountEvent = createEvent(FETCH_COUNT, fetchCounts);
- UsageDataEvent pushCountEvent = createEvent(PUSH_COUNT, pushCounts);
+ List<UsageDataPublishedListener.Event> events = new ArrayList<UsageDataPublishedListener.Event>(3);
+ for (UsageDataEventCreator creator : creators) {
+ try {
+ events.add(creator.create());
+ } catch (RuntimeException e) {
+ String creatorName = creator.getName();
+ log.warn("Exception in usage data event creator " + creatorName, e);
+ }
+ }
+
for (UsageDataPublishedListener l : listeners) {
try {
- l.onUsageDataPublished(repoSizeEvent);
- l.onUsageDataPublished(pushCountEvent);
- l.onUsageDataPublished(fetchCountEvent);
+ for (Event event : events) {
+ l.onUsageDataPublished(event);
+ }
} catch (RuntimeException e) {
- log.warn("Failure in UsageDataPublishedListener", e);
+ log.warn("Exception in UsageDataPublishedListener", e);
}
}
}
- private UsageDataEvent createRepoSizeEvent() {
- UsageDataEvent event = new UsageDataEvent(REPO_SIZE);
- for (Project.NameKey p : projectCache.all()) {
- long size = repoSizeCache.get(p);
- if (size > 0) {
- event.addData(size, p.get());
- }
- }
- return event;
- }
-
- private UsageDataEvent createEvent(MetaData metaData, PersistentCounter counts) {
- UsageDataEvent event = new UsageDataEvent(metaData);
- for (Project.NameKey p : projectCache.all()) {
- long currentCount = counts.getAndReset(p);
- if (currentCount != 0) {
- event.addData(currentCount, p.get());
- }
- }
- return event;
- }
-
- private static class UsageDataEvent implements Event {
-
- private final Timestamp timestamp;
- private final MetaData metaData;
- private final List<Data> data;
-
- public UsageDataEvent(MetaData metaData) {
- this.metaData = metaData;
- timestamp = new Timestamp(System.currentTimeMillis());
- data = new ArrayList<Data>();
- }
-
- private void addData(final long value, final String projectName) {
- Data dataRow = new Data() {
-
- @Override
- public long getValue() {
- return value;
- }
-
- @Override
- public String getProjectName() {
- return projectName;
- }
- };
-
- data.add(dataRow);
- }
-
- @Override
- public MetaData getMetaData() {
- return metaData;
- }
-
- @Override
- public Timestamp getInstant() {
- return timestamp ;
- }
-
- @Override
- public List<Data> getData() {
- return data;
- }
- };
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/quota/RepoSizeEventCreator.java b/src/main/java/com/googlesource/gerrit/plugins/quota/RepoSizeEventCreator.java
new file mode 100644
index 0000000..8293ebe
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/quota/RepoSizeEventCreator.java
@@ -0,0 +1,60 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// 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.googlesource.gerrit.plugins.quota;
+
+import com.google.gerrit.extensions.events.UsageDataPublishedListener.Event;
+import com.google.gerrit.extensions.events.UsageDataPublishedListener.MetaData;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+@Singleton
+public class RepoSizeEventCreator implements UsageDataEventCreator {
+
+ private static final MetaData REPO_SIZE = new MetaDataImpl("repoSize", "byte", "B",
+ "total file size of the repository");
+
+ private final ProjectCache projectCache;
+ private final RepoSizeCache repoSizeCache;
+
+ @Inject
+ public RepoSizeEventCreator(ProjectCache projectCache,
+ RepoSizeCache repoSizeCache) {
+ this.projectCache = projectCache;
+ this.repoSizeCache = repoSizeCache;
+ }
+
+ private UsageDataEvent createRepoSizeEvent() {
+ UsageDataEvent event = new UsageDataEvent(REPO_SIZE);
+ for (Project.NameKey p : projectCache.all()) {
+ long size = repoSizeCache.get(p);
+ if (size > 0) {
+ event.addData(size, p.get());
+ }
+ }
+ return event;
+ }
+
+ @Override
+ public String getName() {
+ return REPO_SIZE.getName();
+ };
+
+ @Override
+ public Event create() {
+ return createRepoSizeEvent();
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/quota/UsageDataEvent.java b/src/main/java/com/googlesource/gerrit/plugins/quota/UsageDataEvent.java
new file mode 100644
index 0000000..c596b09
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/quota/UsageDataEvent.java
@@ -0,0 +1,68 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// 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.googlesource.gerrit.plugins.quota;
+
+import com.google.gerrit.extensions.events.UsageDataPublishedListener.Data;
+import com.google.gerrit.extensions.events.UsageDataPublishedListener.Event;
+import com.google.gerrit.extensions.events.UsageDataPublishedListener.MetaData;
+
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.List;
+
+class UsageDataEvent implements Event {
+
+ private final Timestamp timestamp;
+ private final MetaData metaData;
+ private final List<Data> data;
+
+ public UsageDataEvent(MetaData metaData) {
+ this.metaData = metaData;
+ timestamp = new Timestamp(System.currentTimeMillis());
+ data = new ArrayList<Data>();
+ }
+
+ void addData(final long value, final String projectName) {
+ Data dataRow = new Data() {
+
+ @Override
+ public long getValue() {
+ return value;
+ }
+
+ @Override
+ public String getProjectName() {
+ return projectName;
+ }
+ };
+
+ data.add(dataRow);
+ }
+
+ @Override
+ public MetaData getMetaData() {
+ return metaData;
+ }
+
+ @Override
+ public Timestamp getInstant() {
+ return timestamp ;
+ }
+
+ @Override
+ public List<Data> getData() {
+ return data;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/googlesource/gerrit/plugins/quota/UsageDataEventCreator.java b/src/main/java/com/googlesource/gerrit/plugins/quota/UsageDataEventCreator.java
new file mode 100644
index 0000000..5ad42b9
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/quota/UsageDataEventCreator.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// 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.googlesource.gerrit.plugins.quota;
+
+import com.google.gerrit.extensions.events.UsageDataPublishedListener;
+
+public interface UsageDataEventCreator {
+
+ public String getName();
+
+ public UsageDataPublishedListener.Event create();
+
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/quota/PublisherExceptionTest.java b/src/test/java/com/googlesource/gerrit/plugins/quota/PublisherExceptionTest.java
new file mode 100644
index 0000000..dbe03f8
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/quota/PublisherExceptionTest.java
@@ -0,0 +1,143 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// 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.googlesource.gerrit.plugins.quota;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.*;
+
+import com.google.gerrit.extensions.events.UsageDataPublishedListener;
+import com.google.gerrit.extensions.events.UsageDataPublishedListener.Event;
+import com.google.gerrit.extensions.registration.DynamicSet;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.spi.LoggingEvent;
+import org.easymock.Capture;
+import org.junit.Before;
+import org.junit.Test;
+
+public class PublisherExceptionTest {
+
+ private static final String CREATOR_NAME = "test-creator";
+ private UsageDataPublishedListener listener;
+ private UsageDataEventCreator creator;
+ private Publisher classUnderTest;
+ private Appender appender;
+ private Capture<LoggingEvent> captor;
+ private DynamicSet<UsageDataPublishedListener> listeners;
+ private DynamicSet<UsageDataEventCreator> creators;
+
+ @Before
+ public void setupClassUnderTest() {
+ listener = createMock(UsageDataPublishedListener.class);
+ listeners = DynamicSet.emptySet();
+ listeners.add(listener);
+
+ creator = createMock(UsageDataEventCreator.class);
+ expect(creator.getName()).andStubReturn(CREATOR_NAME);
+ creators = DynamicSet.emptySet();
+ creators.add(creator);
+
+ classUnderTest = new Publisher(listeners, creators);
+ }
+
+ @Before
+ public void setupLogging() {
+ captor = new Capture<LoggingEvent>();
+ appender = createMock(Appender.class);
+ appender.doAppend(capture(captor));
+ expectLastCall().anyTimes();
+ Logger.getRootLogger().addAppender(appender);
+ }
+
+ @Test
+ public void testExceptionInCreatorIsLogged() {
+ RuntimeException ex = new RuntimeException();
+ expect(creator.create()).andStubThrow(ex);
+
+ replay(listener, creator, appender);
+
+ classUnderTest.run();
+
+ verify(listener, creator, appender);
+
+ assertTrue(captor.hasCaptured());
+ LoggingEvent event = captor.getValue();
+ assertEquals(Level.WARN, event.getLevel());
+ assertTrue(((String)event.getMessage()).contains(CREATOR_NAME));
+ }
+
+ @Test
+ public void testDataFromGoodCreatorIsPropagated() {
+ RuntimeException ex = new RuntimeException();
+ expect(creator.create()).andStubThrow(ex);
+
+ UsageDataEventCreator good = createMock(UsageDataEventCreator.class);
+ Event data = new UsageDataEvent(FetchAndPushEventCreator.FETCH_COUNT);
+ expect(good.create()).andStubReturn(data);
+ creators.add(good);
+
+ listener.onUsageDataPublished(data);
+ expectLastCall();
+
+ replay(listener, creator, good, appender);
+
+ classUnderTest.run();
+
+ verify(listener, creator, appender);
+ }
+
+ @Test
+ public void testExceptionInListenerIsLogged() {
+ RuntimeException ex = new RuntimeException();
+ Event data = new UsageDataEvent(FetchAndPushEventCreator.FETCH_COUNT);
+ expect(creator.create()).andStubReturn(data);
+
+ listener.onUsageDataPublished(data);
+ expectLastCall().andStubThrow(ex);
+
+ replay(listener, creator, appender);
+
+ classUnderTest.run();
+
+ verify(listener, creator, appender);
+
+ assertTrue(captor.hasCaptured());
+ LoggingEvent event = captor.getValue();
+ assertEquals(Level.WARN, event.getLevel());
+ }
+
+ @Test
+ public void testIsPropagatedToGoodListener() {
+ RuntimeException ex = new RuntimeException();
+ Event data = new UsageDataEvent(FetchAndPushEventCreator.FETCH_COUNT);
+ expect(creator.create()).andStubReturn(data);
+
+ listener.onUsageDataPublished(data);
+ expectLastCall().andStubThrow(ex);
+
+ UsageDataPublishedListener good = createMock(UsageDataPublishedListener.class);
+ good.onUsageDataPublished(data);
+ listeners.add(good);
+
+ replay(listener, good, creator, appender);
+
+ classUnderTest.run();
+
+ verify(listener, good, creator, appender);
+ }
+
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/quota/PublisherTest.java b/src/test/java/com/googlesource/gerrit/plugins/quota/PublisherTest.java
new file mode 100644
index 0000000..077b056
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/quota/PublisherTest.java
@@ -0,0 +1,106 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// 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.googlesource.gerrit.plugins.quota;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+
+import com.google.gerrit.extensions.events.UsageDataPublishedListener;
+import com.google.gerrit.extensions.events.UsageDataPublishedListener.Event;
+import com.google.gerrit.extensions.registration.DynamicSet;
+
+import org.junit.Test;
+
+public class PublisherTest {
+
+ @Test
+ public void testAllEventsPropagatedToListener() throws Exception {
+ Event e1 = new UsageDataEvent(FetchAndPushEventCreator.FETCH_COUNT);
+ UsageDataEventCreator c1 = createMock(UsageDataEventCreator.class);
+ expect(c1.create()).andStubReturn(e1);
+
+ Event e2 = new UsageDataEvent(FetchAndPushEventCreator.PUSH_COUNT);
+ UsageDataEventCreator c2 = createMock(UsageDataEventCreator.class);
+ expect(c2.create()).andStubReturn(e2);
+
+ DynamicSet<UsageDataEventCreator> creators = DynamicSet.emptySet();
+ creators.add(c1);
+ creators.add(c2);
+
+ UsageDataPublishedListener listener =
+ createMock(UsageDataPublishedListener.class);
+ listener.onUsageDataPublished(e1);
+ expectLastCall();
+ listener.onUsageDataPublished(e2);
+ expectLastCall();
+
+ replay(c1, c2, listener);
+ DynamicSet<UsageDataPublishedListener> listeners = DynamicSet.emptySet();
+ listeners.add(listener);
+
+ Publisher classUnderTest = new Publisher(listeners, creators);
+ classUnderTest.run();
+
+ verify(c1, c2, listener);
+ }
+
+ @Test
+ public void testEventPropagatedToAllListeners() throws Exception {
+ Event event = new UsageDataEvent(FetchAndPushEventCreator.FETCH_COUNT);
+ UsageDataEventCreator creator = createMock(UsageDataEventCreator.class);
+ expect(creator.create()).andStubReturn(event);
+ DynamicSet<UsageDataEventCreator> creators = DynamicSet.emptySet();
+ creators.add(creator);
+
+ UsageDataPublishedListener l1 =
+ createMock(UsageDataPublishedListener.class);
+ l1.onUsageDataPublished(event);
+ expectLastCall();
+
+ UsageDataPublishedListener l2 =
+ createMock(UsageDataPublishedListener.class);
+ l2.onUsageDataPublished(event);
+ expectLastCall();
+
+ replay(creator, l1, l2);
+
+ DynamicSet<UsageDataPublishedListener> listeners = DynamicSet.emptySet();
+ listeners.add(l1);
+ listeners.add(l2);
+
+ Publisher classUnderTest = new Publisher(listeners, creators);
+ classUnderTest.run();
+
+ verify(creator, l1, l2);
+ }
+
+ @Test
+ public void testNoEventsCreatedIfNoListenersRegistered() throws Exception {
+ UsageDataEventCreator creator = createMock(UsageDataEventCreator.class);
+ replay(creator);
+ DynamicSet<UsageDataEventCreator> creators = DynamicSet.emptySet();
+ creators.add(creator);
+
+ DynamicSet<UsageDataPublishedListener> listeners = DynamicSet.emptySet();
+ Publisher classUnderTest = new Publisher(listeners, creators);
+ classUnderTest.run();
+
+ verify(creator);
+ }
+
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/quota/RepoSizeEventCreatorTest.java b/src/test/java/com/googlesource/gerrit/plugins/quota/RepoSizeEventCreatorTest.java
new file mode 100644
index 0000000..f67f776
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/quota/RepoSizeEventCreatorTest.java
@@ -0,0 +1,90 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// 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.googlesource.gerrit.plugins.quota;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.google.gerrit.extensions.events.UsageDataPublishedListener.Data;
+import com.google.gerrit.extensions.events.UsageDataPublishedListener.Event;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gwtorm.client.KeyUtil;
+import com.google.gwtorm.server.StandardKeyEncoder;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+
+public class RepoSizeEventCreatorTest {
+
+ static {
+ KeyUtil.setEncoderImpl(new StandardKeyEncoder());
+ }
+
+ private Project.NameKey p1 = new Project.NameKey("p1");
+ private Project.NameKey p2 = new Project.NameKey("p2");
+ private Project.NameKey p3 = new Project.NameKey("p3");
+ private ProjectCache projectCache;
+ private RepoSizeEventCreator classUnderTest;
+ private File tmp;
+ private RepoSizeCache repoSizeCache;
+
+ @Before
+ public void setup() throws IOException {
+ tmp = File.createTempFile("quota-test", "dir");
+ tmp.delete();
+ tmp.mkdir();
+ projectCache = createMock(ProjectCache.class);
+ Iterable<Project.NameKey> projects = Arrays.asList(p1, p2, p3);
+ expect(projectCache.all()).andStubReturn(projects);
+ repoSizeCache = createNiceMock(RepoSizeCache.class);
+ replay(projectCache);
+ classUnderTest = new RepoSizeEventCreator(projectCache, repoSizeCache);
+ }
+
+
+ @Test
+ public void testEmpty() {
+ replay(repoSizeCache);
+
+ Event event = classUnderTest.create();
+
+ assertEquals("repoSize", event.getMetaData().getName());
+ assertTrue(event.getData().isEmpty());
+ }
+
+ @Test
+ public void testOneDataPoint() {
+ expect(repoSizeCache.get(p1)).andStubReturn(100l);
+ replay(repoSizeCache);
+
+ Event event = classUnderTest.create();
+
+ assertEquals("repoSize", event.getMetaData().getName());
+ assertEquals(1, event.getData().size());
+ Data dataPoint = event.getData().get(0);
+ assertEquals("p1", dataPoint.getProjectName());
+ assertEquals(100l, dataPoint.getValue());
+ }
+
+}