Skip to content

Commit

Permalink
feat: initial gametest setup
Browse files Browse the repository at this point in the history
Stop using Guava RateLimiter as the gametests run ticks faster, and the RateLimiter uses the system
clock so RS wouldn't keep up with the faster tick rate.
  • Loading branch information
raoulvdberge committed May 25, 2024
1 parent be403ac commit e423f42
Show file tree
Hide file tree
Showing 20 changed files with 216 additions and 39 deletions.
55 changes: 37 additions & 18 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ Valid examples are:

## Translations

If you want to contribute to the translations of this project, please use [Crowdin](https://crowdin.com/project/refined-storage-2).
If you want to contribute to the translations of this project, please
use [Crowdin](https://crowdin.com/project/refined-storage-2).

## Versioning

Expand Down Expand Up @@ -126,8 +127,25 @@ at all times.
They ensure that there are no regressions, act as general documentation for the codebase,
and ensure that the project can evolve over time.

To avoid brittle tests, tests need to validate behavior. A test cannot rely on the internal code structure, so most
mocking should be avoided.
To avoid brittle tests, tests need to validate behavior. A test cannot rely on the internal code structure, so most use
of mocking should be avoided.

### Unit testing

Tests in the API modules are regular unit tests. Don't see a "unit" here as a code unit, but as a unit of behavior.

> Don't isolate code by using mocking, even in a unit test.
These tests don't rely on, nor know about, Minecraft.

Additionally, tests in the `refinedstorage2-network` module use the `refinedstorage2-network-test` JUnit plugin to easily set up networks for testing.

### Integration testing

To test the entire chain from Minecraft to the API modules, we use integration tests. These tests are located in the
test source set of the `refinedstorage2-platform-forge` module.

We write these integration tests as Minecraft gametests using the NeoForge testing framework.

### Test coverage

Expand Down Expand Up @@ -202,6 +220,7 @@ The build workflow takes care of the following:

- Running a Gradle build, running our tests in the process and generating an aggregated code coverage report for the API
modules.
- Running Minecraft gametests.
- Analyzing the code on SonarQube.
> Because of
> [limitations with SonarQube](https://portal.productboard.com/sonarsource/1-sonarcloud/c/50-sonarcloud-analyzes-external-pull-request),
Expand Down Expand Up @@ -249,18 +268,18 @@ Refined Storage 2 is split up into various modules.
Most modules aren't dependent on Minecraft or a mod loader. Only modules that start
with `refinedstorage2-platform-*` have dependencies on Minecraft.

| Name | Use in addons | Description |
|-----------------------------------|---------------|---------------------------------------------------------------------------------------|
| `refinedstorage2-core-api` | ✔️ | Contains some utility classes and enums. |
| `refinedstorage2-grid-api` | ✔️ | Contains Grid related functionality. |
| `refinedstorage2-network-api` | ✔️ | Contains storage network related functionality. |
| `refinedstorage2-network` || Contains implementations of `refinedstorage2-network-api`. |
| `refinedstorage2-network-test` | ✔️ | JUnit extension which helps with setting up a network and a network node for testing. |
| `refinedstorage2-query-parser` | ✔️ | A query parser, contains a lexer and parser. Only used for Grid query parsing. |
| `refinedstorage2-resource-api` | ✔️ | Contains API for handling resources. |
| `refinedstorage2-storage-api` | ✔️ | Contains storage related functionality. |
| `refinedstorage2-platform-api` | ✔️ | Implements the various Refined Storage API modules for use in Minecraft. |
| `refinedstorage2-platform-test` | ✔️ | This module is used in platform tests for various Minecraft related helpers. |
| `refinedstorage2-platform-fabric` || The platform module for Fabric. This module contains Fabric specific code. |
| `refinedstorage2-platform-forge` || The platform module for Forge. This module contains Forge specific code. |
| `refinedstorage2-platform-common` || Common mod code. Most gameplay code is in here. |
| Name | Use in addons | Description |
|-----------------------------------|---------------|----------------------------------------------------------------------------------------------------|
| `refinedstorage2-core-api` | ✔️ | Contains some utility classes and enums. |
| `refinedstorage2-grid-api` | ✔️ | Contains Grid related functionality. |
| `refinedstorage2-network-api` | ✔️ | Contains storage network related functionality. |
| `refinedstorage2-network` || Contains implementations of `refinedstorage2-network-api`. |
| `refinedstorage2-network-test` | ✔️ | JUnit extension which helps with setting up a network and a network node for testing. |
| `refinedstorage2-query-parser` | ✔️ | A query parser, contains a lexer and parser. Only used for Grid query parsing. |
| `refinedstorage2-resource-api` | ✔️ | Contains API for handling resources. |
| `refinedstorage2-storage-api` | ✔️ | Contains storage related functionality. |
| `refinedstorage2-platform-api` | ✔️ | Implements the various Refined Storage API modules for use in Minecraft. |
| `refinedstorage2-platform-test` | ✔️ | This module is used in platform tests for various Minecraft related helpers. |
| `refinedstorage2-platform-fabric` || The platform module for Fabric. This module contains Fabric specific code. |
| `refinedstorage2-platform-forge` || The platform module for Forge. This module contains Forge specific code and the integration tests. |
| `refinedstorage2-platform-common` || Common mod code. Most gameplay code is in here. |
4 changes: 3 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ on:
types: [ opened, synchronize, reopened ]
jobs:
build:
uses: refinedmods/refinedarchitect/.github/workflows/[email protected]
uses: refinedmods/refinedarchitect/.github/workflows/[email protected]
with:
gametests: true
secrets: inherit
2 changes: 1 addition & 1 deletion .github/workflows/draft-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ on:
type: string
jobs:
draft:
uses: refinedmods/refinedarchitect/.github/workflows/draft-release.yml@v0.11.5
uses: refinedmods/refinedarchitect/.github/workflows/draft-release.yml@v0.12.1
with:
release-type: ${{ inputs.release-type }}
version-number-override: ${{ inputs.version-number-override }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/issue-for-unsupported-version.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ on:
types: [ labeled, unlabeled, reopened ]
jobs:
unsupported-labeler:
uses: refinedmods/refinedarchitect/.github/workflows/issue-for-unsupported-version.yml@v0.11.5
uses: refinedmods/refinedarchitect/.github/workflows/issue-for-unsupported-version.yml@v0.12.1
2 changes: 1 addition & 1 deletion .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
- closed
jobs:
publish-release:
uses: refinedmods/refinedarchitect/.github/workflows/publish-release.yml@v0.11.5
uses: refinedmods/refinedarchitect/.github/workflows/publish-release.yml@v0.12.1
secrets: inherit
with:
project-name: 'Refined Storage'
2 changes: 1 addition & 1 deletion .github/workflows/resolved-issue-locking.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ on:
- cron: '0 0 * * *'
jobs:
lock:
uses: refinedmods/refinedarchitect/.github/workflows/resolved-issue-locking.yml@v0.11.5
uses: refinedmods/refinedarchitect/.github/workflows/resolved-issue-locking.yml@v0.12.1
2 changes: 1 addition & 1 deletion .github/workflows/validate-branch-name.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ name: Validate branch name
on: [ pull_request ]
jobs:
validate-branch-name:
uses: refinedmods/refinedarchitect/.github/workflows/validate-branch-name.yml@v0.11.5
uses: refinedmods/refinedarchitect/.github/workflows/validate-branch-name.yml@v0.12.1
2 changes: 1 addition & 1 deletion .github/workflows/validate-changelog.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ on:
types: [ opened, synchronize, reopened, ready_for_review, labeled, unlabeled ]
jobs:
validate-changelog:
uses: refinedmods/refinedarchitect/.github/workflows/validate-changelog.yml@v0.11.5
uses: refinedmods/refinedarchitect/.github/workflows/validate-changelog.yml@v0.12.1
2 changes: 1 addition & 1 deletion .github/workflows/validate-commit-messages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ name: Validate commit messages
on: [ pull_request ]
jobs:
validate-commit-messages:
uses: refinedmods/refinedarchitect/.github/workflows/validate-commit-messages.yml@v0.11.5
uses: refinedmods/refinedarchitect/.github/workflows/validate-commit-messages.yml@v0.12.1
1 change: 1 addition & 0 deletions config/checkstyle/checkstyle-suppressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@
<!-- Shadow target contains underscore -->
<suppress checks="MemberName" files="ModelBakerImplMixin.java"/>
<suppress checks="HideUtilityClassConstructor" files="GridClearPacket.java"/>
<suppress checks="HideUtilityClassConstructor" files="TestMod.java"/>
</suppressions>
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public ConstructorBlockEntity(final BlockPos pos, final BlockState state) {
}

@Override
protected void setFilters(final List<ResourceKey> filters) {
public void setFilters(final List<ResourceKey> filters) {
this.tasks.clear();
this.tasks.addAll(filters.stream().map(TaskImpl::new).toList());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ protected void setTaskExecutor(final TaskExecutor<ExporterNetworkNode.TaskContex
}

@Override
protected void setFilters(final List<ResourceKey> filters) {
public void setFilters(final List<ResourceKey> filters) {
mainNode.setFilters(filters);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ public Direction extractDirection(@Nullable final BlockState state) {
return getDirectionType().extractDirection(direction);
}

public BlockState rotated(final T direction) {
return defaultBlockState().setValue(getDirectionType().getProperty(), direction);
}

public static boolean doesBlockStateChangeWarrantNetworkNodeUpdate(final BlockState oldBlockState,
final BlockState newBlockState) {
if (!(newBlockState.getBlock() instanceof AbstractDirectionalBlock<?> newDirectionalBlock)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,5 @@ public void writeScreenOpeningData(final ServerPlayer player, final FriendlyByte

protected abstract void setTaskExecutor(TaskExecutor<C> taskExecutor);

protected abstract void setFilters(List<ResourceKey> filters);
public abstract void setFilters(List<ResourceKey> filters);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import java.util.ArrayList;
import java.util.List;

import com.google.common.util.concurrent.RateLimiter;
import net.minecraft.core.BlockPos;
import net.minecraft.core.NonNullList;
import net.minecraft.nbt.CompoundTag;
Expand All @@ -33,7 +32,8 @@ public abstract class AbstractUpgradeableNetworkNodeContainerBlockEntity<T exten
private static final String TAG_UPGRADES = "u";

protected final UpgradeContainer upgradeContainer;
private RateLimiter rateLimiter = createRateLimiter(0);
private int workTickRate = 20;
private int workTicks;

protected AbstractUpgradeableNetworkNodeContainerBlockEntity(
final BlockEntityType<?> type,
Expand All @@ -52,7 +52,7 @@ protected AbstractUpgradeableNetworkNodeContainerBlockEntity(

@Override
public final void doWork() {
if (rateLimiter.tryAcquire()) {
if (workTicks++ % workTickRate == 0) {
super.doWork();
postDoWork();
}
Expand Down Expand Up @@ -105,16 +105,12 @@ public void load(final CompoundTag tag) {
private void configureAccordingToUpgrades() {
LOGGER.debug("Reconfiguring {} for upgrades", getBlockPos());
final int amountOfSpeedUpgrades = upgradeContainer.getAmount(Items.INSTANCE.getSpeedUpgrade());
this.rateLimiter = createRateLimiter(amountOfSpeedUpgrades);
this.workTickRate = (amountOfSpeedUpgrades + 1) * 20;
this.setEnergyUsage(upgradeContainer.getEnergyUsage());
}

protected abstract void setEnergyUsage(long upgradeEnergyUsage);

private static RateLimiter createRateLimiter(final int amountOfSpeedUpgrades) {
return RateLimiter.create((double) amountOfSpeedUpgrades + 1);
}

@Override
public NonNullList<ItemStack> getDrops() {
final NonNullList<ItemStack> drops = NonNullList.create();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.refinedmods.refinedstorage2.platform.forge;

import com.refinedmods.refinedstorage2.api.resource.ResourceAmount;
import com.refinedmods.refinedstorage2.platform.common.constructordestructor.ConstructorBlockEntity;
import com.refinedmods.refinedstorage2.platform.common.storage.ItemStorageType;
import com.refinedmods.refinedstorage2.platform.common.util.IdentifierUtil;

import java.util.List;

import net.minecraft.core.Direction;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.world.level.block.Blocks;
import net.neoforged.neoforge.gametest.GameTestHolder;
import net.neoforged.neoforge.gametest.PrefixGameTestTemplate;

import static com.refinedmods.refinedstorage2.platform.forge.GameTestUtil.DIRT;
import static com.refinedmods.refinedstorage2.platform.forge.GameTestUtil.RSBLOCKS;
import static com.refinedmods.refinedstorage2.platform.forge.GameTestUtil.itemIsInserted;
import static com.refinedmods.refinedstorage2.platform.forge.GameTestUtil.requireBlockEntity;
import static com.refinedmods.refinedstorage2.platform.forge.GameTestUtil.storageMustContainExactly;
import static net.minecraft.core.BlockPos.ZERO;

@GameTestHolder(IdentifierUtil.MOD_ID)
@PrefixGameTestTemplate(false)
public final class ConstructorTest {
private ConstructorTest() {
}

@GameTest(template = "empty_15x15")
public static void shouldPlaceBlock(final GameTestHelper helper) {
// Arrange
helper.setBlock(ZERO.above(), RSBLOCKS.getCreativeController().getDefault());
helper.setBlock(ZERO.above().above(), RSBLOCKS.getConstructor().getDefault().rotated(Direction.EAST));
helper.setBlock(
ZERO.above().above().above(),
RSBLOCKS.getItemStorageBlock(ItemStorageType.Variant.ONE_K)
);

final var seq = helper.startSequence();
seq.thenWaitUntil(itemIsInserted(helper, ZERO.above().above(), DIRT, 10));

// Act
requireBlockEntity(helper, ZERO.above().above(), ConstructorBlockEntity.class).setFilters(List.of(DIRT));

// Assert
seq.thenWaitUntil(() -> helper.assertBlockPresent(Blocks.DIRT, ZERO.above().above().east()))
.thenWaitUntil(storageMustContainExactly(helper, ZERO.above().above(), new ResourceAmount(DIRT, 9)))
.thenSucceed();
}
}
Loading

0 comments on commit e423f42

Please sign in to comment.