Skip to content

Commit

Permalink
Add some insert tests and attempt to publish test results
Browse files Browse the repository at this point in the history
  • Loading branch information
Tim203 committed Sep 12, 2024
1 parent 20e4b48 commit a95bbd2
Show file tree
Hide file tree
Showing 9 changed files with 299 additions and 92 deletions.
14 changes: 12 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ name: Build

on: [ push ]

permissions:
checks: write
pull-requests: write

jobs:
build:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -31,7 +35,13 @@ jobs:
cache-read-only: ${{ github.ref_name != 'main' && github.ref_name != 'development' }}

- name: Notify Discord
if: ${{ (success() || failure()) && github.repository == 'GeyserMC/DatabaseUtils' }}
if: ${{ always() && github.repository == 'GeyserMC/DatabaseUtils' }}
uses: Tim203/actions-git-discord-webhook@main
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK }}
webhook_url: ${{ secrets.DISCORD_WEBHOOK }}

- name: Publish Test Results
uses: EnricoMi/publish-unit-test-result-action@v2
if: always()
with:
files: "**/build/test-results/**/*.xml"
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,30 @@ public void addExists(QueryContext context, MethodSpec.Builder spec) {

@Override
public void addInsert(QueryContext context, MethodSpec.Builder spec) {
// theoretically currently the getInsertedIds size should match the amount of documents sent,
// since 'ordered' prevents it from inserting the remaining documents in case of a conflict
wrapInCompletableFuture(spec, context.returnInfo().async(), () -> {
if (context.parametersInfo().isSelfCollection()) {
spec.addStatement(
"this.collection.insertMany($L)",
context.parametersInfo().firstName());
var firstName = context.parametersInfo().firstName();
spec.beginControlFlow("if ($L.isEmpty())", firstName);

if (context.typeUtils().isWholeNumberType(context.returnType())) {
spec.addStatement("return 0");
spec.endControlFlow();
spec.addStatement("return this.collection.insertMany($L).getInsertedIds().size()", firstName);
return;
}

spec.addStatement("return $L", context.returnInfo().async() ? "null" : "");
spec.endControlFlow();
spec.addStatement("this.collection.insertMany($L)", firstName);
} else if (context.parametersInfo().isSelf()) {
if (context.typeUtils().isWholeNumberType(context.returnType())) {
spec.addStatement(
"return this.collection.insertOne($L).getInsertedId() != null ? 1 : 0",
context.parametersInfo().firstName());
return;
}
spec.addStatement(
"this.collection.insertOne($L)",
context.parametersInfo().firstName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ public CompletableFuture<Void> insert(TestEntity entity) {

@Override
public void insert(List<TestEntity> entities) {
if (entities.isEmpty()) {
return;
}
this.collection.insertMany(entities);
}

Expand Down
85 changes: 58 additions & 27 deletions core/src/test/java/org/geysermc/databaseutils/TestContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,14 @@
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.oracle.OracleContainer;

/**
* TestContext is meant to be shared across all test classes, meaning that parallel test execution shouldn't be enabled.
*/
public final class TestContext {
public static final TestContext INSTANCE = new TestContext();
// public static final TestContext INSTANCE = new TestContext(DatabaseType.SQLITE, DatabaseType.H2,
// DatabaseType.MONGODB);

@SuppressWarnings("resource")
private final Map<DatabaseType, ? extends GenericContainer<?>> containers = new HashMap<>() {
{
Expand All @@ -42,16 +49,18 @@ public final class TestContext {
}
};

private boolean containersEnabled;

private final Map<DatabaseType, Map<Class<?>, IRepository<?>>> repositoriesForType = new ConcurrentHashMap<>();
private final Set<Class<?>> usedRepositories = new HashSet<>();
private final ExecutorService executor = Executors.newCachedThreadPool();
private final Set<DatabaseType> limitedSet;

public TestContext(DatabaseType... limitedSet) {
private TestContext(DatabaseType... limitedSet) {
this.limitedSet = new HashSet<>(Arrays.asList(limitedSet));
}

public TestContext() {
private TestContext() {
this(DatabaseType.values());
}

Expand All @@ -61,33 +70,26 @@ public final void start(Class<? extends IRepository<?>>... repositories) {
}

public void start(List<Class<? extends IRepository<?>>> repositoryClasses) {
// let's start all the databases at the same time, instead of waiting for each one to be ready
List<CompletableFuture<?>> futures = new ArrayList<>();
for (var entry : containers.entrySet()) {
if (!limitedSet.contains(entry.getKey())) {
continue;
startContainersIfNeeded();

containers.forEach((type, container) -> {
if (!limitedSet.contains(type)) {
return;
}
futures.add(CompletableFuture.runAsync(() -> {
var type = entry.getKey();
var container = entry.getValue();

container.start();

String uri, username = null, password = null;
if (container instanceof JdbcDatabaseContainer<?> jdbcContainer) {
uri = jdbcContainer.getJdbcUrl();
username = jdbcContainer.getUsername();
password = jdbcContainer.getPassword();
} else if (container instanceof MongoDBContainer mongoContainer) {
uri = mongoContainer.getConnectionString() + "/database";
} else {
throw new RuntimeException("Unknown container type: " + type);
}

addRepositoriesFor(type, uri, username, password, repositoryClasses);
}));
}
CompletableFuture.allOf(futures.toArray(new CompletableFuture<?>[0])).join();
String uri, username = null, password = null;
if (container instanceof JdbcDatabaseContainer<?> jdbcContainer) {
uri = jdbcContainer.getJdbcUrl();
username = jdbcContainer.getUsername();
password = jdbcContainer.getPassword();
} else if (container instanceof MongoDBContainer mongoContainer) {
uri = mongoContainer.getConnectionString() + "/database";
} else {
throw new RuntimeException("Unknown container type: " + type);
}

addRepositoriesFor(type, uri, username, password, repositoryClasses);
});

if (limitedSet.contains(DatabaseType.H2)) {
addRepositoriesFor(DatabaseType.H2, null, null, null, repositoryClasses);
Expand All @@ -103,9 +105,33 @@ public void start(List<Class<? extends IRepository<?>>> repositoryClasses) {
}

public void stop() {
// just to make sure every row is deleted, since this instance can be reused
deleteRows();
repositoriesForType.clear();
}

private void shutdown() {
containers.values().forEach(GenericContainer::stop);
}

private void startContainersIfNeeded() {
if (containersEnabled) {
return;
}

// let's start all the databases at the same time, instead of waiting for each one to be ready
List<CompletableFuture<?>> futures = new ArrayList<>();
containers.forEach((type, container) -> {
if (limitedSet.contains(type)) {
futures.add(CompletableFuture.runAsync(container::start));
}
});

System.out.println("Downloading (if needed) and launching all database containers, this may take a while");
CompletableFuture.allOf(futures.toArray(new CompletableFuture<?>[0])).join();
containersEnabled = true;
}

@SuppressWarnings("unchecked")
public <T extends GenericContainer<?>> T container(DatabaseType type) {
return (T) containers.get(type);
Expand Down Expand Up @@ -181,4 +207,9 @@ public void deleteRows() {
}
usedRepositories.clear();
}

static {
// apparently this isn't needed, but I like the safeguard
Runtime.getRuntime().addShutdownHook(new Thread(INSTANCE::shutdown));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT license
* @link https://github.com/GeyserMC/DatabaseUtils
*/
package org.geysermc.databaseutils.delete.repository;
package org.geysermc.databaseutils.delete;

import org.geysermc.databaseutils.IRepository;
import org.geysermc.databaseutils.ReusableTestRepository;
Expand All @@ -15,7 +15,7 @@
public interface DeleteRepository extends IRepository<TestEntity>, ReusableTestRepository {
void insert(TestEntity entity);

TestEntity findByAAndB(int a, String b);
boolean existsByAAndB(int a, String b);

void delete(TestEntity entity);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@
package org.geysermc.databaseutils.delete;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.stream.Stream;
import org.geysermc.databaseutils.DatabaseType;
import org.geysermc.databaseutils.TestContext;
import org.geysermc.databaseutils.delete.repository.DeleteRepository;
import org.geysermc.databaseutils.entity.TestEntity;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
Expand All @@ -21,8 +20,7 @@
import org.junit.jupiter.api.TestFactory;

final class DeleteTests {
static TestContext context = new TestContext();
// static TestContext context = new TestContext(DatabaseType.SQLITE, DatabaseType.H2, DatabaseType.MONGODB);
static TestContext context = TestContext.INSTANCE;

@BeforeAll
static void setUp() {
Expand All @@ -46,13 +44,13 @@ Stream<DynamicTest> delete() {
repository.insert(new TestEntity(1, "hello", "world!", null));
repository.insert(new TestEntity(2, "hello", "world!", null));

assertNotNull(repository.findByAAndB(0, "hello"));
assertNotNull(repository.findByAAndB(1, "hello"));
assertNotNull(repository.findByAAndB(2, "hello"));
assertTrue(repository.existsByAAndB(0, "hello"));
assertTrue(repository.existsByAAndB(1, "hello"));
assertTrue(repository.existsByAAndB(2, "hello"));
repository.delete();
assertNull(repository.findByAAndB(0, "hello"));
assertNull(repository.findByAAndB(1, "hello"));
assertNull(repository.findByAAndB(2, "hello"));
assertFalse(repository.existsByAAndB(0, "hello"));
assertFalse(repository.existsByAAndB(1, "hello"));
assertFalse(repository.existsByAAndB(2, "hello"));
});
}

Expand All @@ -63,11 +61,11 @@ Stream<DynamicTest> deleteSingleEntity() {
repository.insert(new TestEntity(1, "hello", "world!", null));
repository.insert(new TestEntity(2, "hello", "world!", null));

assertNotNull(repository.findByAAndB(1, "hello"));
assertTrue(repository.existsByAAndB(1, "hello"));
repository.delete(new TestEntity(1, "hello", "world!", null));
assertNull(repository.findByAAndB(1, "hello"));
assertNotNull(repository.findByAAndB(0, "hello"));
assertNotNull(repository.findByAAndB(2, "hello"));
assertFalse(repository.existsByAAndB(1, "hello"));
assertTrue(repository.existsByAAndB(0, "hello"));
assertTrue(repository.existsByAAndB(2, "hello"));
});
}

Expand All @@ -78,11 +76,11 @@ Stream<DynamicTest> deleteSingleByKey() {
repository.insert(new TestEntity(1, "hello", "world!", null));
repository.insert(new TestEntity(2, "hello", "world!", null));

assertNotNull(repository.findByAAndB(1, "hello"));
assertTrue(repository.existsByAAndB(1, "hello"));
repository.deleteByAAndB(1, "hello");
assertNull(repository.findByAAndB(1, "hello"));
assertNotNull(repository.findByAAndB(0, "hello"));
assertNotNull(repository.findByAAndB(2, "hello"));
assertFalse(repository.existsByAAndB(1, "hello"));
assertTrue(repository.existsByAAndB(0, "hello"));
assertTrue(repository.existsByAAndB(2, "hello"));
});
}

Expand All @@ -93,13 +91,13 @@ Stream<DynamicTest> deleteNonUnique() {
repository.insert(new TestEntity(1, "hello", "world!", null));
repository.insert(new TestEntity(2, "hello", "world!", null));

assertNotNull(repository.findByAAndB(0, "hello"));
assertNotNull(repository.findByAAndB(1, "hello"));
assertNotNull(repository.findByAAndB(2, "hello"));
assertTrue(repository.existsByAAndB(0, "hello"));
assertTrue(repository.existsByAAndB(1, "hello"));
assertTrue(repository.existsByAAndB(2, "hello"));
assertEquals(3, repository.deleteByB("hello"));
assertNull(repository.findByAAndB(0, "hello"));
assertNull(repository.findByAAndB(1, "hello"));
assertNull(repository.findByAAndB(2, "hello"));
assertFalse(repository.existsByAAndB(0, "hello"));
assertFalse(repository.existsByAAndB(1, "hello"));
assertFalse(repository.existsByAAndB(2, "hello"));
});
}

Expand All @@ -112,11 +110,11 @@ Stream<DynamicTest> deleteFirst() {
repository.insert(new TestEntity(1, "hello", "world!", null));
repository.insert(new TestEntity(2, "hello", "world!", null));

assertNotNull(repository.findByAAndB(1, "hello"));
assertTrue(repository.existsByAAndB(1, "hello"));
assertEquals(1, repository.deleteFirstByB("hello"));
assertNull(repository.findByAAndB(0, "hello"));
assertNotNull(repository.findByAAndB(1, "hello"));
assertNotNull(repository.findByAAndB(2, "hello"));
assertFalse(repository.existsByAAndB(0, "hello"));
assertTrue(repository.existsByAAndB(1, "hello"));
assertTrue(repository.existsByAAndB(2, "hello"));
},
DatabaseType.ORACLE_DATABASE,
DatabaseType.POSTGRESQL,
Expand All @@ -134,14 +132,13 @@ Stream<DynamicTest> deleteReturning() {
repository.insert(new TestEntity(1, "hello", "world!", null));
repository.insert(new TestEntity(2, "hello", "world!", null));

assertNotNull(repository.findByAAndB(1, "hello"));
assertTrue(repository.existsByAAndB(1, "hello"));
var returning = repository.deleteReturning(1, "hello");
assertNotNull(returning);
assertEquals(new TestEntity(1, "hello", "world!", null), returning);

assertNull(repository.findByAAndB(1, "hello"));
assertNotNull(repository.findByAAndB(0, "hello"));
assertNotNull(repository.findByAAndB(2, "hello"));
assertFalse(repository.existsByAAndB(1, "hello"));
assertTrue(repository.existsByAAndB(0, "hello"));
assertTrue(repository.existsByAAndB(2, "hello"));
},
DatabaseType.H2,
DatabaseType.MYSQL,
Expand Down

This file was deleted.

Loading

0 comments on commit a95bbd2

Please sign in to comment.