diff --git a/.gitignore b/.gitignore index c1af677b996..4825b0aa7be 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ extensions /facades/* !/facades/PC !/facades/TeraEd +!/facades/WorkspaceValidation !/facades/subprojects.gradle /modules/* !/modules/subprojects.gradle diff --git a/facades/WorkspaceValidation/.gitignore b/facades/WorkspaceValidation/.gitignore new file mode 100644 index 00000000000..7263b6ee479 --- /dev/null +++ b/facades/WorkspaceValidation/.gitignore @@ -0,0 +1,11 @@ +# Boo eclipse! - every eclipse project needs its own .gitignore file + +# Ignore Eclipse +/.checkstyle +/.project +/.classpath +/.settings/ +/bin/ + +# Ignore gradle +/build/ diff --git a/facades/WorkspaceValidation/build.gradle b/facades/WorkspaceValidation/build.gradle new file mode 100644 index 00000000000..989aa4af735 --- /dev/null +++ b/facades/WorkspaceValidation/build.gradle @@ -0,0 +1,70 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +// The Editor facade is responsible for the (shader) editor - a plain Java application runnable on PCs + +// Grab all the common stuff like plugins to use, artifact repositories, code analysis config +apply from: "$rootDir/config/gradle/publish.gradle" + +// Base the engine tests on the same version number as the engine +version = project(':engine').version +println "TeraEd VERSION: $version" + +// Jenkins-Artifactory integration catches on to this as part of the Maven-type descriptor +group = 'org.terasology.facades' + +sourceSets { + // Adjust output path (changed with the Gradle 6 upgrade, this puts it back) + main.java.outputDir = new File("$buildDir/classes") + test.java.outputDir = new File("$buildDir/testClasses") +} + +dependencies { + implementation project(':engine') + implementation "org.terasology:reflections:0.9.12-MB" + + implementation(platform(project(":modules"))) + + implementation(project(":engine-tests")) + + implementation("org.terasology.modules:ModuleTestingEnvironment:0.3.3-SNAPSHOT") + + implementation(group: 'com.google.guava', name: 'guava', version: '30.1-jre') + + implementation(project(":subsystems:DiscordRPC")) + implementation(project(":subsystems:TypeHandlerLibrary")) + + implementation(group: 'org.lwjglx', name: 'lwjgl3-awt', version: '0.1.7') { + exclude group: 'org.lwjgl', module: '' + } + + // Test lib dependencies + implementation(platform("org.junit:junit-bom:5.8.1")) { + // junit-bom will set version numbers for the other org.junit dependencies. + } + implementation("org.junit.jupiter:junit-jupiter-api") + implementation("org.junit.jupiter:junit-jupiter-params") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") + testImplementation('org.mockito:mockito-inline:3.12.4') +} + +test { +// dependsOn copyResourcesToClasses + dependsOn rootProject.extractNatives + + description("Runs all tests (slow)") + + systemProperty("junit.jupiter.execution.timeout.default", "4m") +} + + + +// Prep an IntelliJ module for the facade +idea { + module { + // Change around the output a bit + inheritOutputDirs = false + outputDir = file('build/classes') + testOutputDir = file('build/testClasses') + } +} diff --git a/facades/WorkspaceValidation/src/test/java/org/terasology/workspace/validation/AssetTesting.java b/facades/WorkspaceValidation/src/test/java/org/terasology/workspace/validation/AssetTesting.java new file mode 100644 index 00000000000..199b6457441 --- /dev/null +++ b/facades/WorkspaceValidation/src/test/java/org/terasology/workspace/validation/AssetTesting.java @@ -0,0 +1,166 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.workspace.validation; + +import com.google.common.io.ByteStreams; +import org.junit.jupiter.api.DynamicContainer; +import org.junit.jupiter.api.DynamicNode; +import org.junit.jupiter.api.DynamicTest; +import org.terasology.engine.core.TerasologyConstants; +import org.terasology.engine.core.module.ModuleManager; +import org.terasology.gestalt.assets.Asset; +import org.terasology.gestalt.assets.AssetData; +import org.terasology.gestalt.assets.ResourceUrn; +import org.terasology.gestalt.assets.management.AssetManager; +import org.terasology.gestalt.entitysystem.component.Component; +import org.terasology.gestalt.module.Module; +import org.terasology.gestalt.module.resources.ModuleFileSource; +import org.terasology.gestalt.naming.Name; +import org.terasology.moduletestingenvironment.Engines; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiPredicate; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; + +/** + * Set of functions for testing assets. + */ +public class AssetTesting { + /** + * Template for assets tests. + *

+ * Provide engine setup, engine tearup and {@code testsCretor}'s content as tests. + * + * @param moduleFilter - filter. which modules should be runs with {@code testsCreator}'s tests. + * @param testsCreator - function which provides asset tests. + * @return Stream of DynamicNodes. requred for junit-jupiter's dynamic tests. + */ + public Stream template( + BiPredicate moduleFilter, + Function, + Stream> testsCreator) { + ModuleManager moduleManager = createModuleManager(); + return moduleManager.getRegistry() + .getModuleIds() + .stream() + // use filter over modules + .filter(name -> moduleFilter.test(moduleManager, name)) + // create tests + .map(moduleName -> { + // Create engine + EnginesAccessor engine = new EnginesAccessor(Set.of(moduleName.toString()), null); + // Provide `Lazy` reference for asset tests + AtomicReference assetManagerRef = new AtomicReference<>(); + return DynamicContainer.dynamicContainer(String.format("Module - %s", moduleName), + Stream.of( + // engine test + DynamicTest.dynamicTest("setup", () -> { + engine.setup(); + assetManagerRef.set(engine.getHostContext().get(AssetManager.class)); + }), + // Container with asset tests + DynamicContainer.dynamicContainer("Assets tests", testsCreator.apply(assetManagerRef)), + // engine teardown test + DynamicTest.dynamicTest("tearDown", engine::tearDown))); + }); + } + + public ModuleManager createModuleManager() { + System.setProperty(ModuleManager.LOAD_CLASSPATH_MODULES_PROPERTY, "true"); + return new ModuleManager(""); + } + + public BiPredicate haveAsset(String assetName) { + return (moduleManager, name) -> { + Module module = moduleManager.getRegistry().getLatestModuleVersion(name); + ModuleFileSource resources = module.getResources(); + + boolean haveAsset = !resources.getFilesInPath(true, "assets/" + assetName).isEmpty(); + + boolean haveAssetOverride = + resources.getFilesInPath(false, "overrides") + .stream() + .anyMatch(fr -> fr.getPath().contains(assetName)); + boolean haveAssetDelta = + resources.getFilesInPath(false, "deltas") + .stream() + .anyMatch(fr -> fr.getPath().contains(assetName)); + + return haveAsset || haveAssetOverride || haveAssetDelta; + }; + } + + public > BiPredicate havePrefab(Class componentClass) { + return (moduleManager, name) -> { + Module module = moduleManager.getRegistry().getLatestModuleVersion(name); + ModuleFileSource resources = module.getResources(); + + return resources.getFiles().stream() + .filter(fr -> fr.toString().contains("prefabs") && fr.toString().contains(".prefab")) + .anyMatch(fr -> { + try (InputStream inputStream = fr.open()) { + byte[] bytes = ByteStreams.toByteArray(inputStream); + String content = new String(bytes, TerasologyConstants.CHARSET); + String componentName = componentClass.getSimpleName(); + return content.contains(componentName); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + }); + }; + } + + public > DynamicContainer asset(AtomicReference assetManager, + Class assetClazz, + Consumer validator) { + return asset(assetManager, assetClazz, (a) -> true, validator); + + } + + public > DynamicContainer asset(AtomicReference assetManager, + Class assetClazz, + Predicate filter, + Consumer validator) { + + return DynamicContainer.dynamicContainer(String.format("AssetType: %s", assetClazz.getSimpleName()), + DynamicTest.stream( + // Special generator for 1 value for `lazy` getting. + Stream.generate(() -> assetManager.get().getAvailableAssets(assetClazz).stream()) + .limit(1) + .flatMap(s -> s) + .filter(urn -> filter.test(assetManager.get().getAsset(urn, assetClazz).get())), + ResourceUrn::toString, + urn -> { + A asset = assetManager.get().getAsset(urn, assetClazz).get(); + validator.accept(asset); + } + )); + + } + + // Just grant access to protected methods. + public static class EnginesAccessor extends Engines { + + public EnginesAccessor(Set dependencies, String worldGeneratorUri) { + super(dependencies, worldGeneratorUri); + } + + @Override + protected void setup() { + super.setup(); + } + + @Override + protected void tearDown() { + super.tearDown(); + } + } +} diff --git a/facades/WorkspaceValidation/src/test/java/org/terasology/workspace/validation/WorkspaceBehaviorsTests.java b/facades/WorkspaceValidation/src/test/java/org/terasology/workspace/validation/WorkspaceBehaviorsTests.java new file mode 100644 index 00000000000..40c8fb750cf --- /dev/null +++ b/facades/WorkspaceValidation/src/test/java/org/terasology/workspace/validation/WorkspaceBehaviorsTests.java @@ -0,0 +1,39 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.workspace.validation; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicNode; +import org.junit.jupiter.api.TestFactory; +import org.terasology.engine.entitySystem.prefab.Prefab; +import org.terasology.engine.logic.behavior.BehaviorComponent; +import org.terasology.engine.logic.behavior.asset.BehaviorTree; + +import java.util.stream.Stream; + +public class WorkspaceBehaviorsTests extends AssetTesting { + + @TestFactory + Stream behaviours() { + return template( + // Filter modules + haveAsset("behaviors").or(havePrefab(BehaviorComponent.class)), + // Create assetTests + assetManagerRef -> Stream.of( + asset( + assetManagerRef, + BehaviorTree.class, + (b) -> Assertions.assertNotNull(b.getData()) + ), + asset( + assetManagerRef, + Prefab.class, + prefab -> prefab.hasComponent(BehaviorComponent.class), + prefab -> Assertions.assertNotNull(prefab.getComponent(BehaviorComponent.class).tree) + + )) + ); + } + +} diff --git a/facades/WorkspaceValidation/src/test/java/org/terasology/workspace/validation/WorkspaceModulesResolvingTests.java b/facades/WorkspaceValidation/src/test/java/org/terasology/workspace/validation/WorkspaceModulesResolvingTests.java new file mode 100644 index 00000000000..0674dee51af --- /dev/null +++ b/facades/WorkspaceValidation/src/test/java/org/terasology/workspace/validation/WorkspaceModulesResolvingTests.java @@ -0,0 +1,56 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.workspace.validation; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.terasology.engine.core.module.ModuleManager; +import org.terasology.gestalt.naming.Name; + +import java.util.Set; +import java.util.stream.Stream; + +/** + * Example test for check modules dependency validations. + */ +public class WorkspaceModulesResolvingTests { + + public static Stream modulesAndModuleManager() { + System.setProperty(ModuleManager.LOAD_CLASSPATH_MODULES_PROPERTY, "true"); + ModuleManager temporary = new ModuleManager(""); + return temporary.getRegistry() + .getModuleIds() + .stream() + .map((name) -> Arguments.of(temporary, name)); + } + + public static Stream modulePairsAndModuleManager() { + System.setProperty(ModuleManager.LOAD_CLASSPATH_MODULES_PROPERTY, "true"); + ModuleManager temporary = new ModuleManager(""); + Set moduleIds = temporary.getRegistry() + .getModuleIds(); + return moduleIds + .stream() + .flatMap((name) -> moduleIds.stream().map((name2) -> Arguments.of(temporary, name, name2))); + } + + @DisplayName("Try to resolve and load module") + @ParameterizedTest(name = "{displayName} - {1}") + @MethodSource("modulesAndModuleManager") + void resolveAndLoadModule(ModuleManager moduleManager, Name moduleName) { + moduleManager.resolveAndLoadEnvironment(moduleName); + Assertions.assertNotNull(moduleManager.getEnvironment()); + } + + @DisplayName("Try to resolve and load pair modules") + @ParameterizedTest(name = "{displayName} - [{1}, {2}]") + @MethodSource("modulePairsAndModuleManager") + void resolveAndLoadPairModules(ModuleManager moduleManager, Name moduleName1, Name moduleName2) { + moduleManager.resolveAndLoadEnvironment(moduleName1, moduleName2); + Assertions.assertNotNull(moduleManager.getEnvironment()); + } +}