Refactor ounit functions

This commit is contained in:
aNNiMON 2024-02-10 22:30:10 +02:00
parent 03d890df7c
commit 31945471dd
3 changed files with 130 additions and 139 deletions

View File

@ -5,7 +5,6 @@ import com.annimon.ownlang.lib.*;
import com.annimon.ownlang.modules.Module;
import java.text.DecimalFormat;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@ -22,110 +21,91 @@ public final class ounit implements Module {
@Override
public Map<String, Function> functions() {
final var result = new LinkedHashMap<String, Function>(16);
result.put("assertEquals", new assertEquals());
result.put("assertNotEquals", new assertNotEquals());
result.put("assertSameType", new assertSameType());
result.put("assertTrue", new assertTrue());
result.put("assertFalse", new assertFalse());
result.put("runTests", new runTests());
return result;
return Map.of(
"assertEquals", this::assertEquals,
"assertNotEquals", this::assertNotEquals,
"assertSameType", this::assertSameType,
"assertTrue", this::assertTrue,
"assertFalse", this::assertFalse,
"runTests", this::runTests
);
}
private static String microsToSeconds(long micros) {
return new DecimalFormat("#0.0000").format(micros / 1000d / 1000d) + " sec";
}
private static class assertEquals implements Function {
@Override
public Value execute(Value[] args) {
Arguments.check(2, args.length);
if (args[0].equals(args[1])) return NumberValue.ONE;
throw new OUnitAssertionException("Values are not equal: "
+ "1: " + args[0] + ", 2: " + args[1]);
}
}
private static class assertNotEquals implements Function {
@Override
public Value execute(Value[] args) {
Arguments.check(2, args.length);
if (!args[0].equals(args[1])) return NumberValue.ONE;
throw new OUnitAssertionException("Values are equal: " + args[0]);
}
}
private static class assertSameType implements Function {
@Override
public Value execute(Value[] args) {
Arguments.check(2, args.length);
if (args[0].type() == args[1].type()) return NumberValue.ONE;
throw new OUnitAssertionException("Types mismatch. "
+ "1: " + Types.typeToString(args[0].type())
+ ", 2: " + Types.typeToString(args[1].type()));
}
}
private static class assertTrue implements Function {
@Override
public Value execute(Value[] args) {
Arguments.check(1, args.length);
if (args[0].asInt() != 0) return NumberValue.ONE;
throw new OUnitAssertionException("Expected true, but found false.");
}
}
private static class assertFalse implements Function {
@Override
public Value execute(Value[] args) {
Arguments.check(1, args.length);
if (args[0].asInt() == 0) return NumberValue.ONE;
throw new OUnitAssertionException("Expected false, but found true.");
}
}
private static class runTests implements Function {
@Override
public Value execute(Value[] args) {
final var testFunctions = ScopeHandler.functions().entrySet().stream()
.filter(e -> e.getKey().toLowerCase().startsWith("test"))
.toList();
List<TestInfo> tests = testFunctions.stream()
.map(e -> runTest(e.getKey(), e.getValue()))
.toList();
int failures = 0;
long summaryTime = 0;
final StringBuilder result = new StringBuilder();
for (TestInfo test : tests) {
if (!test.isPassed) failures++;
summaryTime += test.elapsedTimeInMicros;
result.append(Console.newline());
result.append(test.info());
}
private Value assertEquals(Value[] args) {
Arguments.check(2, args.length);
if (args[0].equals(args[1])) return NumberValue.ONE;
throw new OUnitAssertionException("Values are not equal: "
+ "1: " + args[0] + ", 2: " + args[1]);
}
private Value assertNotEquals(Value[] args) {
Arguments.check(2, args.length);
if (!args[0].equals(args[1])) return NumberValue.ONE;
throw new OUnitAssertionException("Values are equal: " + args[0]);
}
private Value assertSameType(Value[] args) {
Arguments.check(2, args.length);
if (args[0].type() == args[1].type()) return NumberValue.ONE;
throw new OUnitAssertionException("Types mismatch. "
+ "1: " + Types.typeToString(args[0].type())
+ ", 2: " + Types.typeToString(args[1].type()));
}
private Value assertTrue(Value[] args) {
Arguments.check(1, args.length);
if (args[0].asInt() != 0) return NumberValue.ONE;
throw new OUnitAssertionException("Expected true, but found false.");
}
private Value assertFalse(Value[] args) {
Arguments.check(1, args.length);
if (args[0].asInt() == 0) return NumberValue.ONE;
throw new OUnitAssertionException("Expected false, but found true.");
}
private Value runTests(Value[] args) {
final var testFunctions = ScopeHandler.functions().entrySet().stream()
.filter(e -> e.getKey().toLowerCase().startsWith("test"))
.toList();
List<TestInfo> tests = testFunctions.stream()
.map(e -> runTest(e.getKey(), e.getValue()))
.toList();
int failures = 0;
long summaryTime = 0;
final StringBuilder result = new StringBuilder();
for (TestInfo test : tests) {
if (!test.isPassed) failures++;
summaryTime += test.elapsedTimeInMicros;
result.append(Console.newline());
result.append(String.format("Tests run: %d, Failures: %d, Time elapsed: %s",
tests.size(), failures,
microsToSeconds(summaryTime)));
return new StringValue(result.toString());
result.append(test.info());
}
result.append(Console.newline());
result.append(String.format("Tests run: %d, Failures: %d, Time elapsed: %s",
tests.size(), failures,
microsToSeconds(summaryTime)));
return new StringValue(result.toString());
}
private TestInfo runTest(String name, Function f) {
final long startTime = System.nanoTime();
boolean isSuccessfull;
String failureDescription;
try {
f.execute();
isSuccessfull = true;
failureDescription = "";
} catch (OUnitAssertionException oae) {
isSuccessfull = false;
failureDescription = oae.getMessage();
}
final long elapsedTime = System.nanoTime() - startTime;
return new TestInfo(name, isSuccessfull, failureDescription, elapsedTime / 1000);
private TestInfo runTest(String name, Function f) {
final long startTime = System.nanoTime();
boolean isSuccessfull;
String failureDescription;
try {
f.execute();
isSuccessfull = true;
failureDescription = "";
} catch (OUnitAssertionException oae) {
isSuccessfull = false;
failureDescription = oae.getMessage();
}
final long elapsedTime = System.nanoTime() - startTime;
return new TestInfo(name, isSuccessfull, failureDescription, elapsedTime / 1000);
}
private static class OUnitAssertionException extends RuntimeException {
@ -142,7 +122,7 @@ public final class ounit implements Module {
long elapsedTimeInMicros
) {
public String info() {
return String.format("%s [%s]\n%sElapsed: %s\n",
return "%s [%s]\n%sElapsed: %s\n".formatted(
name,
isPassed ? "passed" : "FAILED",
isPassed ? "" : (failureDescription + "\n"),

View File

@ -0,0 +1,52 @@
package com.annimon.ownlang.parser;
import com.annimon.ownlang.lib.FunctionValue;
import com.annimon.ownlang.lib.NumberValue;
import com.annimon.ownlang.lib.ScopeHandler;
import com.annimon.ownlang.parser.ast.Node;
import com.annimon.ownlang.stages.Stage;
import com.annimon.ownlang.stages.StagesData;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.fail;
public class MockOUnitStage implements Stage<Node, Node> {
@Override
public Node perform(StagesData stagesData, Node input) {
ScopeHandler.resetScope();
ScopeHandler.setFunction("assertEquals", (args) -> {
assertEquals(args[0], args[1]);
return NumberValue.ONE;
});
ScopeHandler.setFunction("assertNotEquals", (args) -> {
assertNotEquals(args[0], args[1]);
return NumberValue.ONE;
});
ScopeHandler.setFunction("assertSameType", (args) -> {
assertEquals(args[0].type(), args[1].type());
return NumberValue.ONE;
});
ScopeHandler.setFunction("assertTrue", (args) -> {
assertTrue(args[0].asInt() != 0);
return NumberValue.ONE;
});
ScopeHandler.setFunction("assertFalse", (args) -> {
assertFalse(args[0].asInt() != 0);
return NumberValue.ONE;
});
ScopeHandler.setFunction("assertFail", (args) -> {
assertThrows(Throwable.class,
() -> ((FunctionValue) args[0]).getValue().execute());
return NumberValue.ONE;
});
ScopeHandler.setFunction("fail", (args) -> {
if (args.length > 0) {
fail(args[0].asString());
} else {
fail();
}
return NumberValue.ONE;
});
return input;
}
}

View File

@ -2,8 +2,6 @@ package com.annimon.ownlang.parser;
import com.annimon.ownlang.Console;
import com.annimon.ownlang.exceptions.OwnLangParserException;
import com.annimon.ownlang.lib.FunctionValue;
import com.annimon.ownlang.lib.NumberValue;
import com.annimon.ownlang.lib.ScopeHandler;
import com.annimon.ownlang.parser.ast.ClassDeclarationStatement;
import com.annimon.ownlang.parser.ast.FunctionDefineStatement;
@ -42,7 +40,7 @@ public class ProgramsTest {
.then(new ParserStage())
.then(new LinterStage(LinterStage.Mode.SEMANTIC))
.thenConditional(true, new OptimizationStage(9))
.then(ProgramsTest::mockOUnit)
.then(new MockOUnitStage())
.then(new ExecutionStage())
.then((stagesData, input) -> {
input.accept(testFunctionsExecutor);
@ -50,45 +48,6 @@ public class ProgramsTest {
});
}
private static Node mockOUnit(StagesData stagesData, Node input) {
ScopeHandler.resetScope();
// Let's mock junit methods as ounit functions
ScopeHandler.setFunction("assertEquals", (args) -> {
assertEquals(args[0], args[1]);
return NumberValue.ONE;
});
ScopeHandler.setFunction("assertNotEquals", (args) -> {
assertNotEquals(args[0], args[1]);
return NumberValue.ONE;
});
ScopeHandler.setFunction("assertSameType", (args) -> {
assertEquals(args[0].type(), args[1].type());
return NumberValue.ONE;
});
ScopeHandler.setFunction("assertTrue", (args) -> {
assertTrue(args[0].asInt() != 0);
return NumberValue.ONE;
});
ScopeHandler.setFunction("assertFalse", (args) -> {
assertFalse(args[0].asInt() != 0);
return NumberValue.ONE;
});
ScopeHandler.setFunction("assertFail", (args) -> {
assertThrows(Throwable.class,
() -> ((FunctionValue) args[0]).getValue().execute());
return NumberValue.ONE;
});
ScopeHandler.setFunction("fail", (args) -> {
if (args.length > 0) {
fail(args[0].asString());
} else {
fail();
}
return NumberValue.ONE;
});
return input;
}
@ParameterizedTest
@MethodSource("data")
public void testProgram(InputSource inputSource) {
@ -119,7 +78,7 @@ public class ProgramsTest {
@Override
public void visit(ClassDeclarationStatement s) {
// skip for tests
}
};
}