Skip to content

Commit

Permalink
feat: Add tests for plugin annotation processors + Add default values…
Browse files Browse the repository at this point in the history
… for PluginMetadata
  • Loading branch information
phinner committed May 4, 2024
1 parent 378411d commit d43d5e7
Show file tree
Hide file tree
Showing 9 changed files with 575 additions and 9 deletions.
2 changes: 2 additions & 0 deletions distributor-common-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ plugins {

dependencies {
compileOnly(projects.distributorInternalAnnotations)
testImplementation(projects.distributorCommon)
annotationProcessor(libs.immutables.processor)
compileOnlyApi(libs.slf4j.api)
testImplementation(libs.slf4j.simple)
compileOnlyApi(libs.bundles.mindustry)
testImplementation(libs.bundles.mindustry)
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,21 +118,34 @@ static PluginMetadata from(final ClassLoader classLoader) throws IOException {
/**
* Returns the display name of the plugin.
*/
String getDisplayName();
@Value.Default
default String getDisplayName() {
return this.getName();
}

/**
* Returns the author of the plugin.
*/
String getAuthor();
@Value.Default
default String getAuthor() {
return "Unknown";
}

/**
* Returns the description of the plugin.
*/
String getDescription();
@Value.Default
default String getDescription() {
return "";
}

/**
* Returns the version of the plugin.
*/
String getVersion();
@Value.Default
default String getVersion() {
return "1.0.0";
}

/**
* Returns the main class of the plugin.
Expand All @@ -142,22 +155,34 @@ static PluginMetadata from(final ClassLoader classLoader) throws IOException {
/**
* Returns the minimum game version required by the plugin.
*/
int getMinGameVersion();
@Value.Default
default int getMinGameVersion() {
return 146;
}

/**
* Returns the GitHub repository of the plugin. Empty if not specified.
*/
String getRepository();
@Value.Default
default String getRepository() {
return "";
}

/**
* Returns the dependencies of the plugin.
*/
List<String> getDependencies();
@Value.Default
default List<String> getDependencies() {
return List.of();
}

/**
* Returns the soft dependencies of the plugin.
*/
List<String> getSoftDependencies();
@Value.Default
default List<String> getSoftDependencies() {
return List.of();
}

sealed interface Builder permits PluginMetadataImpl.Builder {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Distributor, a feature-rich framework for Mindustry plugins.
*
* Copyright (C) 2024 Xpdustry
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.xpdustry.distributor.api.annotation;

import java.util.Optional;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

public final class CompositeAnnotationProcessorTest {

@Test
void test_process() {
final PluginAnnotationProcessor<String> processor1 = $ -> Optional.of("A");
final PluginAnnotationProcessor<String> processor2 = $ -> Optional.of("B");
final PluginAnnotationProcessor<String> processor3 = $ -> Optional.empty();
final PluginAnnotationProcessor<String> processor4 = $ -> Optional.of("D");

final var composite1 = (CompositeAnnotationProcessor) PluginAnnotationProcessor.compose(processor1, processor2);
assertThat(composite1.processors()).containsExactly(processor1, processor2);

final var composite2 =
(CompositeAnnotationProcessor) PluginAnnotationProcessor.compose(composite1, processor3, processor4);
assertThat(composite2.processors()).containsExactly(processor1, processor2, processor3, processor4);

final var instance = new Object();
final var result = composite2.process(instance);
assertThat(result).isPresent();
assertThat(result.get()).containsExactly("A", "B", "D");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/*
* Distributor, a feature-rich framework for Mindustry plugins.
*
* Copyright (C) 2024 Xpdustry
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.xpdustry.distributor.api.annotation;

import arc.Events;
import com.xpdustry.distributor.api.Distributor;
import com.xpdustry.distributor.api.DistributorProvider;
import com.xpdustry.distributor.api.event.EventBus;
import com.xpdustry.distributor.api.scheduler.PluginScheduler;
import com.xpdustry.distributor.api.test.ManageSchedulerExtension;
import com.xpdustry.distributor.api.test.TestPlugin;
import com.xpdustry.distributor.api.test.TestScheduler;
import com.xpdustry.distributor.api.util.Priority;
import com.xpdustry.distributor.event.EventBusImpl;
import com.xpdustry.distributor.scheduler.PluginSchedulerImpl;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

@ExtendWith(ManageSchedulerExtension.class)
@SuppressWarnings({"UnusedMethod", "UnusedVariable"})
public final class EventHandlerProcessorTest {

private static final Duration TIMEOUT = Duration.ofSeconds(1L);

private EventHandlerProcessor processor;
private @TestScheduler PluginScheduler scheduler;
private EventBus events;

@BeforeEach
void setup() {
this.processor = new EventHandlerProcessor(new TestPlugin("test"));
this.events = new EventBusImpl();
final var distributor = Mockito.mock(Distributor.class);
Mockito.when(distributor.getPluginScheduler()).thenReturn(scheduler);
Mockito.when(distributor.getEventBus()).thenReturn(this.events);
DistributorProvider.set(distributor);
}

@AfterEach
void clear() {
DistributorProvider.clear();
Events.clear(); // TODO Add dedicated injection annotation for EventBus
}

@Test
void test_simple() {
final var instance = new TestSimple();
final var event = new TestEvent("Hello, world!");
this.processor.process(instance);
this.events.post(event);
assertThat(instance.event).isEqualTo(event);
}

@Test
void test_priority() {
final var instance = new TestPriority();
final var event = new TestEvent("Hello, world!");
this.processor.process(instance);
this.events.post(event);
assertThat(instance.numbers).containsExactly(1, 2, 3);
}

@Test
void test_no_parameter() {
assertThatThrownBy(() -> this.processor.process(new TestNoParameter()))
.isInstanceOf(IllegalArgumentException.class);
}

@Test
void test_too_many_parameters() {
assertThatThrownBy(() -> this.processor.process(new TestTooManyParameters()))
.isInstanceOf(IllegalArgumentException.class);
}

@Test
void test_async() {
final var instance = new TestAsync();
final var event = new TestEvent("Hello, world!");
this.processor.process(instance);

this.events.post(event);
assertThat(instance.future)
.succeedsWithin(TIMEOUT, InstanceOfAssertFactories.STRING)
.startsWith(PluginSchedulerImpl.DISTRIBUTOR_WORKER_BASE_NAME);
assertThat(instance.event).isEqualTo(event);
}

private static final class TestSimple {

public @Nullable TestEvent event = null;

@EventHandler
public void event(final TestEvent event) {
if (this.event != null) {
throw new IllegalStateException("Event is not null.");
}
this.event = event;
}
}

private static final class TestPriority {

public final List<Integer> numbers = new ArrayList<>();

@EventHandler(priority = Priority.LOW)
public void event1(final TestEvent event) {
this.numbers.add(3);
}

@EventHandler(priority = Priority.HIGH)
public void event2(final TestEvent event) {
this.numbers.add(1);
}

@EventHandler(priority = Priority.NORMAL)
public void event3(final TestEvent event) {
this.numbers.add(2);
}
}

private static final class TestNoParameter {

@EventHandler
public void event() {}
}

private static final class TestTooManyParameters {

@EventHandler
public void event(final TestEvent event1, final TestEvent event2) {}
}

private static final class TestAsync {

public volatile @Nullable TestEvent event = null;
public final CompletableFuture<String> future = new CompletableFuture<>();

@EventHandler
@Async
public void event(final TestEvent event) {
if (this.event != null) {
throw new IllegalStateException("Event is not null.");
}
this.event = event;
this.future.complete(Thread.currentThread().getName());
}
}

private record TestEvent(String message) {}
}
Loading

0 comments on commit d43d5e7

Please sign in to comment.