-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feature(WorkspaceValidationTests): add possible to create workspace-wide tests #4949
base: develop
Are you sure you want to change the base?
Changes from all commits
873d9df
3a8c58e
d81d35b
907ea8f
4c635b1
34ef3e7
fb1b63f
55eca21
b71438e
eaee6ba
11a655a
dc4bdb2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# Boo eclipse! - every eclipse project needs its own .gitignore file | ||
|
||
# Ignore Eclipse | ||
/.checkstyle | ||
/.project | ||
/.classpath | ||
/.settings/ | ||
/bin/ | ||
|
||
# Ignore gradle | ||
/build/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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') | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. | ||
* <p> | ||
* 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<DynamicNode> template( | ||
BiPredicate<ModuleManager, Name> moduleFilter, | ||
Function<AtomicReference<AssetManager>, | ||
Stream<DynamicNode>> 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<AssetManager> 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<ModuleManager, Name> 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 <T extends Component<T>> BiPredicate<ModuleManager, Name> havePrefab(Class<T> 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 <T extends AssetData, A extends Asset<T>> DynamicContainer asset(AtomicReference<AssetManager> assetManager, | ||
Class<A> assetClazz, | ||
Consumer<A> validator) { | ||
return asset(assetManager, assetClazz, (a) -> true, validator); | ||
|
||
} | ||
|
||
public <T extends AssetData, A extends Asset<T>> DynamicContainer asset(AtomicReference<AssetManager> assetManager, | ||
Class<A> assetClazz, | ||
Predicate<A> filter, | ||
Consumer<A> 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<String> dependencies, String worldGeneratorUri) { | ||
super(dependencies, worldGeneratorUri); | ||
} | ||
|
||
@Override | ||
protected void setup() { | ||
super.setup(); | ||
} | ||
|
||
@Override | ||
protected void tearDown() { | ||
super.tearDown(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<DynamicNode> 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) | ||
|
||
)) | ||
); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Arguments> 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<Arguments> modulePairsAndModuleManager() { | ||
System.setProperty(ModuleManager.LOAD_CLASSPATH_MODULES_PROPERTY, "true"); | ||
ModuleManager temporary = new ModuleManager(""); | ||
Set<Name> 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()); | ||
Comment on lines
+52
to
+54
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have you confirmed this can actually fail? I'm wondering because of Terasology/ModuleTestingEnvironment#69 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, i cannot. |
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I have been trying to deprecate ModuleManager#getRegistry, because for the most part, other things being able to mess with the registry that ModuleManager is responsible for managing is bad for business.
Here you are using it to get all the modules in the current workspace. We could add a method to return this sort of read-only collection of module IDs, that would be pretty safe.
I'm guessing the other use case we have for wanting to see all the workspace's modules is the Advanced Game Setup screen where you pick which modules to activate. That uses a lot more than just the ID... I haven't looked at it to find out what interface it really wants.