diff --git a/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/BaseExpressionTest.java b/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/BaseExpressionTest.java new file mode 100644 index 000000000..2cce5ca41 --- /dev/null +++ b/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/BaseExpressionTest.java @@ -0,0 +1,100 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.internal.expression; + +import com.sk89q.worldedit.LocalConfiguration; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.extension.platform.Platform; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Common setup code for expression tests. + */ +class BaseExpressionTest { + + static double readSlot(Expression expr, String name) { + return expr.getSlots().getSlotValue(name).orElseThrow(IllegalStateException::new); + } + + private Platform mockPlat = mock(Platform.class); + + @BeforeEach + void setup() { + when(mockPlat.getConfiguration()).thenReturn(new LocalConfiguration() { + @Override + public void load() { + } + }); + WorldEdit.getInstance().getPlatformManager().register(mockPlat); + } + + @AfterEach + void tearDown() { + WorldEdit.getInstance().getPlatformManager().unregister(mockPlat); + } + + double simpleEval(String expressionString) throws ExpressionException { + final Expression expression = compile(expressionString); + + expression.setEnvironment(new ExpressionEnvironment() { + @Override + public int getBlockType(double x, double y, double z) { + return (int) x; + } + + @Override + public int getBlockData(double x, double y, double z) { + return (int) y; + } + + @Override + public int getBlockTypeAbs(double x, double y, double z) { + return (int) x*10; + } + + @Override + public int getBlockDataAbs(double x, double y, double z) { + return (int) y*10; + } + + @Override + public int getBlockTypeRel(double x, double y, double z) { + return (int) x*100; + } + + @Override + public int getBlockDataRel(double x, double y, double z) { + return (int) y*100; + } + }); + + return expression.evaluate(); + } + + Expression compile(String expressionString, String... variableNames) throws ExpressionException { + final Expression expression = Expression.compile(expressionString, variableNames); + expression.optimize(); + return expression; + } +} diff --git a/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/ExpressionTest.java b/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/ExpressionTest.java index a8a30ba40..5520c660e 100644 --- a/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/ExpressionTest.java +++ b/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/ExpressionTest.java @@ -19,11 +19,6 @@ package com.sk89q.worldedit.internal.expression; -import com.sk89q.worldedit.LocalConfiguration; -import com.sk89q.worldedit.WorldEdit; -import com.sk89q.worldedit.extension.platform.Platform; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.time.Duration; @@ -34,27 +29,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -public class ExpressionTest { - - private Platform mockPlat = mock(Platform.class); - - @BeforeEach - public void setup() { - when(mockPlat.getConfiguration()).thenReturn(new LocalConfiguration() { - @Override - public void load() { - } - }); - WorldEdit.getInstance().getPlatformManager().register(mockPlat); - } - - @AfterEach - public void tearDown() { - WorldEdit.getInstance().getPlatformManager().unregister(mockPlat); - } +class ExpressionTest extends BaseExpressionTest { @Test public void testEvaluate() throws ExpressionException { @@ -192,47 +168,4 @@ public class ExpressionTest { assertTrue(e.getMessage().contains("Calculations exceeded time limit")); } - private double simpleEval(String expressionString) throws ExpressionException { - final Expression expression = compile(expressionString); - - expression.setEnvironment(new ExpressionEnvironment() { - @Override - public int getBlockType(double x, double y, double z) { - return (int) x; - } - - @Override - public int getBlockData(double x, double y, double z) { - return (int) y; - } - - @Override - public int getBlockTypeAbs(double x, double y, double z) { - return (int) x*10; - } - - @Override - public int getBlockDataAbs(double x, double y, double z) { - return (int) y*10; - } - - @Override - public int getBlockTypeRel(double x, double y, double z) { - return (int) x*100; - } - - @Override - public int getBlockDataRel(double x, double y, double z) { - return (int) y*100; - } - }); - - return expression.evaluate(); - } - - private Expression compile(String expressionString, String... variableNames) throws ExpressionException { - final Expression expression = Expression.compile(expressionString, variableNames); - expression.optimize(); - return expression; - } } diff --git a/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/RealExpressionTest.java b/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/RealExpressionTest.java new file mode 100644 index 000000000..625e023a1 --- /dev/null +++ b/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/RealExpressionTest.java @@ -0,0 +1,171 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.internal.expression; + +import com.sk89q.worldedit.math.Vector3; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Test class for various real-world expressions. + */ +class RealExpressionTest extends BaseExpressionTest { + + private static final class TestCase { + + final Vector3 loc; + final double result; + final Consumer postChecks; + + private TestCase(Vector3 loc, double result, Consumer postChecks) { + this.loc = loc; + this.result = result; + this.postChecks = postChecks; + } + + TestCase withData(int expectedData) { + return new TestCase(loc, result, expr -> { + postChecks.accept(expr); + double data = readSlot(expr, "data"); + assertEquals(expectedData, (int) data, + "Test case " + this + " failed (data)"); + }); + } + + @Override + public String toString() { + return loc + " -> " + result; + } + } + + private static TestCase testCase(Vector3 loc, double result) { + return testCase(loc, result, e -> { + }); + } + + private static TestCase testCase(Vector3 loc, double result, Consumer postChecks) { + return new TestCase(loc, result, postChecks); + } + + private void checkExpression(String expr, TestCase... cases) { + Expression compiled = compile(expr, "x", "y", "z"); + for (TestCase aCase : cases) { + Vector3 loc = aCase.loc; + assertEquals(aCase.result, compiled.evaluate(loc.getX(), loc.getY(), loc.getZ()), 0, + "Test case " + aCase + " failed (result)"); + aCase.postChecks.accept(compiled); + } + } + + @Test + void torus() { + checkExpression("(0.75-sqrt(x^2+y^2))^2+z^2 < 0.25^2", + testCase(Vector3.at(0, 0, 0), 0), + testCase(Vector3.at(0.5, 0.5, 0.5), 0), + testCase(Vector3.at(1, 0, 0), 0), + testCase(Vector3.at(0.5, 0.5, 0), 1), + testCase(Vector3.at(0.75, 0.5, 0), 1), + testCase(Vector3.at(0.75, 0, 0), 1)); + } + + @Test + void gnarledOakTree() { + checkExpression("(0.5+sin(atan2(x,z)*8)*0.2)*(sqrt(x*x+z*z)/0.5)^(-2)-1.2 < y", + testCase(Vector3.at(-1, -1, -1), 1), + testCase(Vector3.at(-1, 0, 1), 1), + testCase(Vector3.at(1, 1, 1), 1), + testCase(Vector3.at(0, 0, -1), 1), + testCase(Vector3.at(0, 0, 0), 0), + testCase(Vector3.at(0, 1, 0), 0), + testCase(Vector3.at(0, 0, 0.32274), 0), + testCase(Vector3.at(0, 0, 0.32275), 1)); + } + + @Test + void rainbowTorus() { + checkExpression("data=(32+15/2/pi*atan2(x,y))%16; (0.75-sqrt(x^2+y^2))^2+z^2 < 0.25^2", + testCase(Vector3.at(0, 0, 0), 0), + testCase(Vector3.at(0.5, 0.5, 0.5), 0), + testCase(Vector3.at(1, 0, 0), 0), + testCase(Vector3.at(0.5, 0.5, 0), 1).withData(1), + testCase(Vector3.at(0.75, 0.5, 0), 1).withData(2), + testCase(Vector3.at(0.75, 0, 0), 1).withData(3)); + } + + @Test + void rainbowEgg() { + TestCase[] testCases = new TestCase[15]; + for (int i = 0; i < testCases.length; i++) { + testCases[i] = testCase(Vector3.at(0, i / 16.0 - 0.5, 0), 1) + .withData((i + 9) % 16); + } + testCases = Stream.concat(Stream.of(testCases), Stream.of( + testCase(Vector3.at(0, 1, 0), 0) + )).toArray(TestCase[]::new); + checkExpression("data=(32+y*16+1)%16; y^2/9+x^2/6*(1/(1-0.4*y))+z^2/6*(1/(1-0.4*y))<0.08", + testCases); + } + + @Test + void heart() { + checkExpression("(z/2)^2+x^2+(5*y/4-sqrt(abs(x)))^2<0.6", + testCase(Vector3.at(0, 0, -1), 1), + testCase(Vector3.at(0, 1, -1), 0), + testCase(Vector3.at(-0.5, 1, 0), 1)); + } + + @Test + void sineWave() { + checkExpression("sin(x*5)/2-0.03", + testCase(Vector3.at(0, 0, 0), 1), + testCase(Vector3.at(0, 1, 0), 1), + testCase(Vector3.at(0, 1, 1), 1), + testCase(Vector3.at(1, 1, 1), 1), + testCase(Vector3.at(0, 0, 1), 0), + testCase(Vector3.at(1, 0, 1), 0)); + } +}