blob: aab695c4a660db7874ac5d433f08ba30434ebc15 [file] [log] [blame]
// Copyright (C) 2017 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.ericsson.gerrit.plugins.gcconductor.postgresqueue;
import static com.ericsson.gerrit.plugins.gcconductor.postgresqueue.TestUtil.configMockFor;
import static com.ericsson.gerrit.plugins.gcconductor.postgresqueue.TestUtil.newContainer;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.ericsson.gerrit.plugins.gcconductor.GcQueueException;
import com.ericsson.gerrit.plugins.gcconductor.RepositoryInfo;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import org.apache.commons.dbcp.BasicDataSource;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.testcontainers.containers.PostgreSQLContainer;
public class PostgresQueueTest {
private BasicDataSource dataSource;
private PostgresQueue queue;
private static PostgreSQLContainer<?> container;
@BeforeClass
public static void startPostgres() {
container = newContainer();
container.start();
}
@Before
public void setUp() throws Exception {
dataSource = new PostgresModule(null).provideGcDatabaseAccess(configMockFor(container));
queue = new PostgresQueue(dataSource);
emptyQueue();
}
@After
public void tearDown() throws Exception {
if (dataSource != null) {
dataSource.close();
}
}
@Test
public void shouldCreateSchemaOnInit() throws Exception {
assertThat(queue.list()).isEmpty();
}
@Test
public void shouldThrowExceptionIfFailsToCreateSchemaOnInit() throws Exception {
BasicDataSource dataSouceMock = mock(BasicDataSource.class);
when(dataSouceMock.getConnection()).thenThrow(new SQLException("some message"));
assertThrows(SQLException.class, () -> queue = new PostgresQueue(dataSouceMock));
}
@Test
public void testAddContainsAndRemove() throws Exception {
String repoPath = "/some/path/to/some/repository";
String hostname = "someHostname";
assertThat(queue.list()).isEmpty();
assertThat(queue.contains(repoPath)).isFalse();
queue.add(repoPath, hostname);
assertThat(queue.list().size()).isEqualTo(1);
assertThat(queue.contains(repoPath)).isTrue();
queue.add(repoPath, hostname);
assertThat(queue.list().size()).isEqualTo(1);
assertThat(queue.contains(repoPath)).isTrue();
String repoPath2 = "/some/path/to/some/repository2";
String hostname2 = "someHostname2";
queue.add(repoPath2, hostname2);
assertThat(queue.list().size()).isEqualTo(2);
assertThat(queue.contains(repoPath)).isTrue();
assertThat(queue.contains(repoPath2)).isTrue();
queue.remove(repoPath2);
assertThat(queue.list().size()).isEqualTo(1);
assertThat(queue.contains(repoPath)).isTrue();
assertThat(queue.contains(repoPath2)).isFalse();
queue.remove(repoPath);
assertThat(queue.list().size()).isEqualTo(0);
assertThat(queue.contains(repoPath)).isFalse();
queue.remove(repoPath);
assertThat(queue.list().size()).isEqualTo(0);
assertThat(queue.contains(repoPath)).isFalse();
}
@Test
public void testAddThatFailsWhenGettingConnection() throws Exception {
queue = new PostgresQueue(createDataSourceThatFailsWhenGettingConnection());
assertThrows(GcQueueException.class, () -> queue.add("repo", "hostname"));
}
@Test
public void testAddThatFailsWhenCreatingStatement() throws Exception {
queue = new PostgresQueue(createDataSourceThatFailsWhenCreatingStatement());
assertThrows(GcQueueException.class, () -> queue.add("repo", "hostname"));
}
@Test
public void testAddThatFailsWhenExecutingQuery() throws Exception {
queue = new PostgresQueue(createDataSourceThatFailsWhenExecutingQuery());
assertThrows(GcQueueException.class, () -> queue.add("repo", "hostname"));
}
@Test
public void testContainsThatFailsWhenGettingConnection() throws Exception {
queue = new PostgresQueue(createDataSourceThatFailsWhenGettingConnection());
assertThrows(GcQueueException.class, () -> queue.contains("repo"));
}
@Test
public void testContainsThatFailsWhenCreatingStatement() throws Exception {
queue = new PostgresQueue(createDataSourceThatFailsWhenCreatingStatement());
assertThrows(GcQueueException.class, () -> queue.contains("repo"));
}
@Test
public void testContainsThatFailsWhenExecutingQuery() throws Exception {
queue = new PostgresQueue(createDataSourceThatFailsWhenExecutingQuery());
assertThrows(GcQueueException.class, () -> queue.contains("repo"));
}
@Test
public void testContainsThatFailsWhenIteratingResults() throws Exception {
queue = new PostgresQueue(createDataSourceThatFailsWhenIteratingResults());
assertThrows(GcQueueException.class, () -> queue.contains("repo"));
}
@Test
public void testRemoveThatFailsWhenGettingConnection() throws Exception {
queue = new PostgresQueue(createDataSourceThatFailsWhenGettingConnection());
assertThrows(GcQueueException.class, () -> queue.remove("repo"));
}
@Test
public void testRemoveThatFailsWhenCreatingStatement() throws Exception {
queue = new PostgresQueue(createDataSourceThatFailsWhenCreatingStatement());
assertThrows(GcQueueException.class, () -> queue.remove("repo"));
}
@Test
public void testRemoveThatFailsWhenExecutingQuery() throws Exception {
queue = new PostgresQueue(createDataSourceThatFailsWhenExecutingQuery());
assertThrows(GcQueueException.class, () -> queue.remove("repo"));
}
@Test
public void testList() throws Exception {
String repoPath = "/some/path/to/some/repository.git";
String repoPath2 = "/some/path/to/some/repository2.git";
String hostname = "hostname";
String executor = "hostname-1";
assertThat(queue.list()).isEmpty();
Timestamp before = new Timestamp(System.currentTimeMillis());
queue.add(repoPath, hostname);
queue.add(repoPath2, hostname);
queue.pick(executor, 0, Optional.empty());
assertThat(queue.list().size()).isEqualTo(2);
assertThat(queue.list().get(0).getPath()).isEqualTo(repoPath);
assertThat(queue.list().get(0).getExecutor()).isEqualTo(executor);
assertThat(queue.list().get(0).getQueuedAt()).isAtLeast(before);
assertTimestampDiff(queue.list().get(0).getQueuedAt());
assertThat(queue.list().get(0).getQueuedFrom()).isEqualTo(hostname);
assertThat(queue.list().get(1).getPath()).isEqualTo(repoPath2);
assertThat(queue.list().get(1).getExecutor()).isNull();
assertThat(queue.list().get(1).getQueuedAt()).isAtLeast(queue.list().get(0).getQueuedAt());
assertTimestampDiff(queue.list().get(1).getQueuedAt());
assertThat(queue.list().get(1).getQueuedFrom()).isEqualTo(hostname);
}
private void assertTimestampDiff(Timestamp actual) {
long timestampDiff = Math.abs(actual.getTime() - System.currentTimeMillis());
assertThat(timestampDiff).isAtMost(TimeUnit.SECONDS.toMillis(1));
}
@Test
public void testListThatFailsWhenGettingConnection() throws Exception {
queue = new PostgresQueue(createDataSourceThatFailsWhenGettingConnection());
assertThrows(GcQueueException.class, () -> queue.list());
}
@Test
public void testListThatFailsWhenCreatingStatement() throws Exception {
queue = new PostgresQueue(createDataSourceThatFailsWhenCreatingStatement());
assertThrows(GcQueueException.class, () -> queue.list());
}
@Test
public void testListThatFailsWhenExecutingQuery() throws Exception {
queue = new PostgresQueue(createDataSourceThatFailsWhenExecutingQuery());
assertThrows(GcQueueException.class, () -> queue.list());
}
@Test
public void testListThatFailsWhenIteratingResults() throws Exception {
queue = new PostgresQueue(createDataSourceThatFailsWhenIteratingResults());
assertThrows(GcQueueException.class, () -> queue.list());
}
@Test
public void testPick() throws Exception {
String repoPath = "/some/path/to/some/repository";
String hostname = "someHostname";
String executor = "someExecutor";
String executor2 = "someExecutor2";
// queue is empty nothing to pick
assertThat(queue.list()).isEmpty();
assertThat(queue.pick(executor, 0, Optional.empty())).isNull();
// queue contains 1 repository, should pick that one
queue.add(repoPath, hostname);
RepositoryInfo picked = queue.pick(executor, 0, Optional.empty());
assertThat(picked).isNotNull();
assertThat(picked.getPath()).isEqualTo(repoPath);
assertThat(picked.getExecutor()).isEqualTo(executor);
// queue contains 1 already picked repository, should pick same one
picked = queue.pick(executor, 0, Optional.empty());
assertThat(picked).isNotNull();
assertThat(picked.getPath()).isEqualTo(repoPath);
assertThat(picked.getExecutor()).isEqualTo(executor);
// queue contains 1 already picked repository, nothing to pick for other
// executors
assertThat(queue.pick(executor2, 0, Optional.empty())).isNull();
}
@Test
public void testPickRepositoriesInOrder() throws Exception {
String repositoryFormat = "my/path%s.git";
for (int i = 0; i < 100; i++) {
queue.add(String.format(repositoryFormat, i), "someHostname");
}
for (int i = 0; i < 100; i++) {
String pickedRepo = queue.pick("someExecutor", 0, Optional.empty()).getPath();
assertThat(pickedRepo).isEqualTo(String.format(repositoryFormat, i));
queue.remove(pickedRepo);
}
}
@Test
public void testPickInQueueForLongerThan() throws Exception {
String repoPath = "/some/path/to/some/repository";
String hostname = "someHostname";
String executor = "someExecutor";
// pick repository older than 10 seconds, nothing to pick
queue.add(repoPath, hostname);
assertThat(queue.pick(executor, 10, Optional.empty())).isNull();
assertThat(queue.list().get(0).getExecutor()).isNull();
// make 2 seconds elapse and pick repository older than 1 second, should pick one
TimeUnit.SECONDS.sleep((2));
RepositoryInfo picked = queue.pick(executor, 1, Optional.empty());
assertThat(picked.getPath()).isEqualTo(repoPath);
assertThat(picked.getExecutor()).isEqualTo(executor);
}
@Test
public void testPickQueuedFrom() throws Exception {
String repoPath = "/some/path/to/some/repository";
String hostname = "hostname";
String otherHostname = "otherHostname";
String executor = "hostname-1";
// pick repository queued from otherHostname, nothing to pick
queue.add(repoPath, hostname);
assertThat(queue.pick(executor, 0, Optional.of(otherHostname))).isNull();
assertThat(queue.list().get(0).getExecutor()).isNull();
// pick repository queued from hostname, should pick one
RepositoryInfo picked = queue.pick(executor, 0, Optional.of(hostname));
assertThat(picked.getPath()).isEqualTo(repoPath);
assertThat(picked.getExecutor()).isEqualTo(executor);
}
@Test
public void testPickThatFailsWhenGettingConnection() throws Exception {
queue = new PostgresQueue(createDataSourceThatFailsWhenGettingConnection());
assertThrows(GcQueueException.class, () -> queue.pick("executor", 0, Optional.empty()));
}
@Test
public void testPickThatFailsWhenCreatingStatement() throws Exception {
queue = new PostgresQueue(createDataSourceThatFailsWhenCreatingStatement());
assertThrows(GcQueueException.class, () -> queue.pick("executor", 0, Optional.empty()));
}
@Test
public void testPickThatFailsWhenExecutingQuery() throws Exception {
queue = new PostgresQueue(createDataSourceThatFailsWhenExecutingQuery());
assertThrows(GcQueueException.class, () -> queue.pick("executor", 0, Optional.empty()));
}
@Test
public void testPickThatFailsWhenIteratingResults() throws Exception {
queue = new PostgresQueue(createDataSourceThatFailsWhenIteratingResults());
assertThrows(GcQueueException.class, () -> queue.pick("executor", 0, Optional.empty()));
}
@Test
public void testUnpick() throws Exception {
String repoPath = "/some/path/to/some/repository";
String hostname = "someHostname";
String executor = "someExecutor";
// queue contains 1 repository, should pick that one
queue.add(repoPath, hostname);
RepositoryInfo picked = queue.pick(executor, 0, Optional.empty());
assertThat(picked.getPath()).isEqualTo(repoPath);
assertThat(picked.getExecutor()).isEqualTo(executor);
queue.unpick(repoPath);
// unpick repo so should pick that one again
queue.unpick(repoPath);
picked = queue.pick(executor, 0, Optional.empty());
assertThat(picked.getPath()).isEqualTo(repoPath);
assertThat(picked.getExecutor()).isEqualTo(executor);
}
@Test
public void testUnpickThatFailsWhenGettingConnection() throws Exception {
queue = new PostgresQueue(createDataSourceThatFailsWhenGettingConnection());
assertThrows(GcQueueException.class, () -> queue.unpick("/some/path/to/some/repository.git"));
}
@Test
public void testUnpickFailsWhenCreatingStatement() throws Exception {
queue = new PostgresQueue(createDataSourceThatFailsWhenCreatingStatement());
assertThrows(GcQueueException.class, () -> queue.unpick("/some/path/to/some/repository.git"));
}
@Test
public void testUnpickThatFailsWhenExecutingQuery() throws Exception {
queue = new PostgresQueue(createDataSourceThatFailsWhenExecutingQuery());
assertThrows(GcQueueException.class, () -> queue.unpick("/some/path/to/some/repository.git"));
}
@Test
public void testResetQueuedFrom() throws Exception {
String repoPath = "/some/path/to/some/repository";
String repoPath2 = "/some/path/to/some/repository2";
String hostname = "hostname";
String otherHostname = "otherHostname";
queue.add(repoPath, hostname);
queue.add(repoPath2, hostname);
assertThat(queue.list().get(0).getQueuedFrom()).isEqualTo(hostname);
assertThat(queue.list().get(1).getQueuedFrom()).isEqualTo(hostname);
queue.resetQueuedFrom(otherHostname);
assertThat(queue.list().get(0).getQueuedFrom()).isEqualTo(otherHostname);
assertThat(queue.list().get(1).getQueuedFrom()).isEqualTo(otherHostname);
}
@Test
public void testResetQueuedFromThatFailsWhenGettingConnection() throws Exception {
queue = new PostgresQueue(createDataSourceThatFailsWhenGettingConnection());
assertThrows(GcQueueException.class, () -> queue.resetQueuedFrom("someHostname"));
}
@Test
public void testResetQueuedFromFailsWhenCreatingStatement() throws Exception {
queue = new PostgresQueue(createDataSourceThatFailsWhenCreatingStatement());
assertThrows(GcQueueException.class, () -> queue.resetQueuedFrom("someHostname"));
}
@Test
public void testResetQueuedFromThatFailsWhenExecutingQuery() throws Exception {
queue = new PostgresQueue(createDataSourceThatFailsWhenExecutingQuery());
assertThrows(GcQueueException.class, () -> queue.resetQueuedFrom("someHostname"));
}
@Test
public void testBumpToFirst() throws Exception {
String repoPath = "/some/path/to/some/repository";
String repoPath2 = "/some/path/to/some/repository2";
String repoPath3 = "/some/path/to/some/repository3";
String hostname = "hostname";
// Queue contains 1 repository, bumping should have no effect
queue.add(repoPath, hostname);
assertThat(queue.list().get(0).getPath()).isEqualTo(repoPath);
queue.bumpToFirst(repoPath);
assertThat(queue.list().get(0).getPath()).isEqualTo(repoPath);
// Queue has 3 repositories, should be able to change their order
queue.add(repoPath2, hostname);
queue.add(repoPath3, hostname);
assertThat(queue.list().get(1).getPath()).isEqualTo(repoPath2);
assertThat(queue.list().get(2).getPath()).isEqualTo(repoPath3);
// repoPath3 should be first, all other repositories should be shifted down
queue.bumpToFirst(repoPath3);
assertThat(queue.list().get(0).getPath()).isEqualTo(repoPath3);
assertThat(queue.list().get(1).getPath()).isEqualTo(repoPath);
assertThat(queue.list().get(2).getPath()).isEqualTo(repoPath2);
// Bumping a repository that is already first priority should have no effect
queue.bumpToFirst(repoPath3);
assertThat(queue.list().get(0).getPath()).isEqualTo(repoPath3);
assertThat(queue.list().get(1).getPath()).isEqualTo(repoPath);
assertThat(queue.list().get(2).getPath()).isEqualTo(repoPath2);
}
@Test
public void testBumpToFirstThatFailsWhenGettingConnection() throws Exception {
queue = new PostgresQueue(createDataSourceThatFailsWhenGettingConnection());
assertThrows(GcQueueException.class, () -> queue.bumpToFirst("someHostname"));
}
@Test
public void testBumpToFirstFailsWhenCreatingStatement() throws Exception {
queue = new PostgresQueue(createDataSourceThatFailsWhenCreatingStatement());
assertThrows(GcQueueException.class, () -> queue.bumpToFirst("someHostname"));
}
@Test
public void testBumpToFirstThatFailsWhenExecutingQuery() throws Exception {
queue = new PostgresQueue(createDataSourceThatFailsWhenExecutingQuery());
assertThrows(GcQueueException.class, () -> queue.bumpToFirst("someHostname"));
}
private BasicDataSource createDataSourceThatFailsWhenGettingConnection() throws Exception {
BasicDataSource dataSouceMock = mock(BasicDataSource.class);
Connection connectionMock = mock(Connection.class);
Statement statementMock = mock(Statement.class);
when(dataSouceMock.getConnection()).thenReturn(connectionMock).thenThrow(new SQLException());
when(connectionMock.createStatement()).thenReturn(statementMock);
return dataSouceMock;
}
private BasicDataSource createDataSourceThatFailsWhenCreatingStatement() throws Exception {
BasicDataSource dataSouceMock = mock(BasicDataSource.class);
Connection connectionMock = mock(Connection.class);
Statement statementMock = mock(Statement.class);
when(dataSouceMock.getConnection()).thenReturn(connectionMock);
when(connectionMock.createStatement()).thenReturn(statementMock).thenThrow(new SQLException());
return dataSouceMock;
}
private BasicDataSource createDataSourceThatFailsWhenExecutingQuery() throws Exception {
BasicDataSource dataSouceMock = mock(BasicDataSource.class);
Connection connectionMock = mock(Connection.class);
Statement statementMock = mock(Statement.class);
when(dataSouceMock.getConnection()).thenReturn(connectionMock);
when(connectionMock.createStatement()).thenReturn(statementMock);
when(statementMock.execute(anyString())).thenReturn(true).thenThrow(new SQLException());
when(statementMock.executeQuery(anyString())).thenThrow(new SQLException());
return dataSouceMock;
}
private BasicDataSource createDataSourceThatFailsWhenIteratingResults() throws Exception {
BasicDataSource dataSouceMock = mock(BasicDataSource.class);
Connection connectionMock = mock(Connection.class);
Statement statementMock = mock(Statement.class);
ResultSet resultSetMock = mock(ResultSet.class);
when(dataSouceMock.getConnection()).thenReturn(connectionMock);
when(connectionMock.createStatement()).thenReturn(statementMock);
when(statementMock.executeQuery(anyString())).thenReturn(resultSetMock);
when(resultSetMock.next()).thenThrow(new SQLException());
return dataSouceMock;
}
private void emptyQueue() throws Exception {
queue.list().stream().map(RepositoryInfo::getPath).forEach(this::doEmptyQueue);
assertThat(queue.list()).isEmpty();
}
private void doEmptyQueue(String repository) {
try {
queue.remove(repository);
} catch (GcQueueException e) {
throw new RuntimeException(e);
}
}
}