Test Framework for Enso (#998)

This commit is contained in:
Marcin Kostrzewa 2020-07-16 15:53:27 +02:00 committed by GitHub
parent f068509b2e
commit 40f44b5b9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 468 additions and 21 deletions

View File

@ -24,10 +24,10 @@ Number.times = act ->
res = here.reverse_list (go Nil this)
res
measure = act -> label -> iter_size -> num_iters ->
single_call = input ->
measure = ~act -> label -> iter_size -> num_iters ->
single_call = _ ->
x1 = System.nano_time
Runtime.no_inline (act input)
Runtime.no_inline act
x2 = System.nano_time
x2 - x1
iteration = it_num ->

View File

@ -0,0 +1,97 @@
## PRIVATE
A helper for the `map` function.
Uses unsafe field mutation under the hood, to rewrite `map` in
a tail-recursive manner. The mutation is purely internal and does not leak
to the user-facing API.
map_helper list cons f = case list of
Cons h t ->
res = Cons (f h) Nil
Unsafe.set_atom_field cons 1 res
here.map_helper t res f
Nil -> Unsafe.set_atom_field cons 1 Nil
## The basic cons-list type.
A cons-list allows to store an arbitrary number of elements.
Prepending to the list can be achieved by using the `Cons` constructor,
while an empty list is represented by `Nil`.
> Example
A list containing the elements `1`, `2`, and `3`, in this order is:
Cons 1 (Cons 2 (Cons 3 Nil))
type List
Nil
Cons
## Applies a function to each element of the list, returning the list of
results.
> Example
In the following example, we add `1` to each element of the list:
(Cons 0 <| Cons 1 <| Cons 2 <| Nil) . map +1
The result of running the code above is:
Cons 1 <| Cons 2 <| Cons 3 <| Nil
map : (Any -> Any) -> List
map f = case this of
Nil -> Nil
Cons h t ->
res = Cons (f h) Nil
here.map_helper t res f
res
## Applies a function to each element of the list.
Unlike `map`, this method does not return the individual results,
therefore it is only useful for side-effecting computations.
> Example
In the following example, we're printing each element of the list
to the standard output:
(Cons 0 <| Cons 1 <| Cons 2 <| Nil) . each IO.println
each : (Any -> Any) -> Unit
each f = case this of
Nil -> Unit
Cons h t ->
f h
t.each f
## Combines all the elements of the list, by iteratively applying the
passed function with next elements of the list.
In general, the result of
(Cons l0 <| Cons l1 <| ... <| Cons ln) . fold init f
is the same as
f (...(f (f init l0) l1)...) ln
> Example
In the following example, we'll compute the sum of all elements of a
list:
(Cons 0 <| Cons 1 <| Cons 2 <| Nil) . fold 0 (+)
fold : Any -> (Any -> Any -> Any) -> Any
fold init f = case this of
Nil -> init
Cons h t -> t.fold (f init h) f
## Reverses the list, returning a list with the same elements, but in the
opposite order.
reverse : List
reverse = this.fold Nil (l -> el -> Cons el l)
## Computes the number of elements in the list.
length : Number
length = this.fold 0 (acc -> _ -> acc + 1)
## Checks whether any element of the list matches the given predicate.
A predicate is a function that takes a list element and returns
a Boolean value.
> Example
In the following example, we'll check if any element of the list is
larger than `1`:
(Cons 0 <| Cons 1 <| Cons 2 <| Nil) . any (> 5)
any : (Any -> Boolean) -> Boolean
any predicate = case this of
Nil -> False
Cons h t -> if predicate h then True else t.any predicate

View File

@ -0,0 +1,111 @@
import Base.List
## The top-level entry point for a test suite.
type Suite specs
## PRIVATE
type Spec name behaviors
## PRIVATE
type Behavior name result
## PRIVATE
Behavior.is_fail = this.result.is_fail
## PRIVATE
Spec.is_fail = this.behaviors.any is_fail
## PRIVATE
Suite.is_fail = this.specs.any is_fail
## PRIVATE
type Assertion
type Success
type Fail message
is_fail = case this of
Success -> False
Fail _ -> True
## Asserts that `this` value is equal to the expected value.
Any.should_equal that = case this == that of
True -> Success
False ->
msg = this.to_text + " did not equal " + that.to_text + "."
Panic.throw (Fail msg)
## Asserts that the given `Boolean` is `True`
Boolean.should_be_true = case this of
True -> Success
False -> Panic.throw (Fail "Expected False to be True.")
## Asserts that the given `Boolean` is `False`
Boolean.should_be_false = case this of
True -> Panic.throw (Fail "Expected True to be False.")
False -> Success
## PRIVATE
Spec.print_report =
IO.print_err (this.name + ":")
this.behaviors.reverse.each behavior->
case behavior.result of
Success ->
IO.print_err (" - " + behavior.name)
Fail msg ->
IO.print_err (" - [FAILED] " + behavior.name)
IO.print_err (" Reason: " + msg)
## Creates a new test group, desribing properties of the object
described by `this`.
> Example
Suite.run <|
describe "Number" <|
it "should define addition" <|
2+3 . should_equal 5
it "should define multiplication" <|
2*3 . should_equal 6
Text.describe ~behaviors =
r = State.run Spec (Spec this Nil) <|
behaviors
State.get Spec
r.print_report
suite = State.get Suite
new_suite = Suite (Cons r suite.specs)
State.put Suite new_suite
## Specifies a single behavior, described by `this`.
> Example
Suite.run <|
describe "Number" <|
it "should define addition" <|
2+3 . should_equal 5
it "should define multiplication" <|
2*3 . should_equal 6
Text.it ~behavior =
spec = State.get Spec
maybeExc = case Panic.recover behavior of
_ -> Success
result = maybeExc.catch ex->
case ex of
Fail _ -> ex
_ -> Fail ("Unexpected error has been thrown: " + ex.to_text)
new_spec = Spec spec.name (Cons (Behavior this result) spec.behaviors)
State.put Spec new_spec
## Runs a suite of tests, consisting of multiple `describe` blocks.
> Example
Suite.run <|
describe "Number" <|
it "should define addition" <|
2+3 . should_equal 5
it "should define multiplication" <|
2*3 . should_equal 6
Suite.run ~specs =
r = State.run Suite (Suite Nil) <|
specs
State.get Suite
code = if r.is_fail then 1 else 0
System.exit code

View File

@ -0,0 +1,38 @@
package org.enso.interpreter.node.expression.atom;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.nodes.RootNode;
import org.enso.interpreter.runtime.callable.atom.Atom;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.state.Stateful;
@NodeInfo(shortName = "get_field", description = "A base for auto-generated Atom getters.")
public class GetFieldNode extends RootNode {
private final int index;
/**
* Creates a new instance of this node.
*
* @param language the current language instance.
* @param index the index this node should use for field lookup.
*/
public GetFieldNode(TruffleLanguage<?> language, int index) {
super(language);
this.index = index;
}
/**
* Executes the node, by taking the first argument from the frame and plucking the proper field
* from it.
*
* @param frame current execution frame
* @return the field value at predefined index
*/
public Stateful execute(VirtualFrame frame) {
Atom atom = (Atom) Function.ArgumentsHelper.getPositionalArguments(frame.getArguments())[0];
Object state = Function.ArgumentsHelper.getState(frame.getArguments());
return new Stateful(state, atom.getFields()[index]);
}
}

View File

@ -0,0 +1,33 @@
package org.enso.interpreter.node.expression.builtin.function;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.dsl.MonadicState;
import org.enso.interpreter.node.callable.InvokeCallableNode;
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
import org.enso.interpreter.runtime.callable.argument.Thunk;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.state.Stateful;
@BuiltinMethod(
type = "Function",
name = "<|",
description = "Takes a function and an argument and applies the function to the argument.",
alwaysDirect = false)
public class ApplicationOperator extends Node {
private @Child InvokeCallableNode invokeCallableNode;
ApplicationOperator() {
invokeCallableNode =
InvokeCallableNode.build(
new CallArgumentInfo[] {new CallArgumentInfo()},
InvokeCallableNode.DefaultsExecutionMode.EXECUTE,
InvokeCallableNode.ArgumentsExecutionMode.EXECUTE);
invokeCallableNode.markTail();
}
Stateful execute(VirtualFrame frame, @MonadicState Object state, Function _this, Thunk argument) {
return invokeCallableNode.execute(_this, frame, state, new Object[] {argument});
}
}

View File

@ -0,0 +1,14 @@
package org.enso.interpreter.node.expression.builtin.runtime;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.dsl.BuiltinMethod;
@BuiltinMethod(type="Runtime", name="gc", description = "Forces garbage collection")
public class GCNode extends Node {
@CompilerDirectives.TruffleBoundary
Object execute(Object _this) {
System.gc();
return 0;
}
}

View File

@ -0,0 +1,17 @@
package org.enso.interpreter.node.expression.builtin.system;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.dsl.BuiltinMethod;
@BuiltinMethod(
type = "System",
name = "exit",
description = "Exits the process, returning the provided code.")
public class ExitNode extends Node {
@CompilerDirectives.TruffleBoundary
Object execute(Object _this, long code) {
System.exit((int) code);
return null;
}
}

View File

@ -0,0 +1,16 @@
package org.enso.interpreter.node.expression.builtin.unsafe;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.runtime.callable.atom.Atom;
@BuiltinMethod(
type = "Unsafe",
name = "set_atom_field",
description = "Unsafely, in place, sets the value of an atom field by index.")
public class SetAtomFieldNode extends Node {
Atom execute(Object _this, Atom atom, long index, Object value) {
atom.getFields()[(int) index] = value;
return atom;
}
}

View File

@ -6,18 +6,22 @@ import org.enso.interpreter.Language;
import org.enso.interpreter.node.expression.builtin.debug.DebugBreakpointMethodGen;
import org.enso.interpreter.node.expression.builtin.debug.DebugEvalMethodGen;
import org.enso.interpreter.node.expression.builtin.error.*;
import org.enso.interpreter.node.expression.builtin.function.ApplicationOperatorMethodGen;
import org.enso.interpreter.node.expression.builtin.function.ExplicitCallFunctionMethodGen;
import org.enso.interpreter.node.expression.builtin.interop.generic.*;
import org.enso.interpreter.node.expression.builtin.interop.syntax.MethodDispatchNode;
import org.enso.interpreter.node.expression.builtin.interop.syntax.ConstructorDispatchNode;
import org.enso.interpreter.node.expression.builtin.io.*;
import org.enso.interpreter.node.expression.builtin.number.*;
import org.enso.interpreter.node.expression.builtin.runtime.GCMethodGen;
import org.enso.interpreter.node.expression.builtin.runtime.NoInlineMethodGen;
import org.enso.interpreter.node.expression.builtin.state.*;
import org.enso.interpreter.node.expression.builtin.system.ExitMethodGen;
import org.enso.interpreter.node.expression.builtin.system.NanoTimeMethodGen;
import org.enso.interpreter.node.expression.builtin.interop.java.*;
import org.enso.interpreter.node.expression.builtin.text.*;
import org.enso.interpreter.node.expression.builtin.thread.WithInterruptHandlerMethodGen;
import org.enso.interpreter.node.expression.builtin.unsafe.SetAtomFieldMethodGen;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.Module;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
@ -78,7 +82,7 @@ public class Builtins {
new AtomConstructor("Cons", scope)
.initializeFields(
new ArgumentDefinition(0, "head", ArgumentDefinition.ExecutionMode.EXECUTE),
new ArgumentDefinition(1, "rest", ArgumentDefinition.ExecutionMode.EXECUTE));
new ArgumentDefinition(1, "tail", ArgumentDefinition.ExecutionMode.EXECUTE));
AtomConstructor io = new AtomConstructor("IO", scope).initializeFields();
AtomConstructor system = new AtomConstructor("System", scope).initializeFields();
AtomConstructor runtime = new AtomConstructor("Runtime", scope).initializeFields();
@ -89,6 +93,8 @@ public class Builtins {
AtomConstructor java = new AtomConstructor("Java", scope).initializeFields();
AtomConstructor thread = new AtomConstructor("Thread", scope).initializeFields();
AtomConstructor unsafe = new AtomConstructor("Unsafe", scope).initializeFields();
scope.registerConstructor(unit);
scope.registerConstructor(any);
scope.registerConstructor(number);
@ -108,6 +114,8 @@ public class Builtins {
scope.registerConstructor(java);
scope.registerConstructor(thread);
scope.registerConstructor(unsafe);
createPolyglot(language);
scope.registerMethod(io, "println", PrintlnMethodGen.makeFunction(language));
@ -115,7 +123,9 @@ public class Builtins {
scope.registerMethod(io, "readln", ReadlnMethodGen.makeFunction(language));
scope.registerMethod(system, "nano_time", NanoTimeMethodGen.makeFunction(language));
scope.registerMethod(system, "exit", ExitMethodGen.makeFunction(language));
scope.registerMethod(runtime, "no_inline", NoInlineMethodGen.makeFunction(language));
scope.registerMethod(runtime, "gc", GCMethodGen.makeFunction(language));
scope.registerMethod(panic, "throw", ThrowPanicMethodGen.makeFunction(language));
scope.registerMethod(panic, "recover", CatchPanicMethodGen.makeFunction(language));
@ -138,6 +148,7 @@ public class Builtins {
scope.registerMethod(debug, "breakpoint", DebugBreakpointMethodGen.makeFunction(language));
scope.registerMethod(function, "call", ExplicitCallFunctionMethodGen.makeFunction(language));
scope.registerMethod(function, "<|", ApplicationOperatorMethodGen.makeFunction(language));
scope.registerMethod(text, "+", ConcatMethodGen.makeFunction(language));
scope.registerMethod(any, "to_text", AnyToTextMethodGen.makeFunction(language));
@ -149,6 +160,8 @@ public class Builtins {
scope.registerMethod(
thread, "with_interrupt_handler", WithInterruptHandlerMethodGen.makeFunction(language));
scope.registerMethod(unsafe, "set_atom_field", SetAtomFieldMethodGen.makeFunction(language));
interopDispatchRoot = Truffle.getRuntime().createCallTarget(MethodDispatchNode.build(language));
interopDispatchSchema =
new FunctionSchema(

View File

@ -14,7 +14,7 @@ import java.util.stream.Collectors;
@ExportLibrary(InteropLibrary.class)
public class Atom implements TruffleObject {
private final AtomConstructor constructor;
private @CompilationFinal(dimensions = 1) Object[] fields;
private final Object[] fields;
/**
* Creates a new Atom for a given constructor.

View File

@ -11,6 +11,7 @@ import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.nodes.RootNode;
import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.node.callable.argument.ReadArgumentNode;
import org.enso.interpreter.node.expression.atom.GetFieldNode;
import org.enso.interpreter.node.expression.atom.InstantiateNode;
import org.enso.interpreter.node.expression.builtin.InstantiateAtomNode;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
@ -18,6 +19,9 @@ import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.callable.function.FunctionSchema;
import org.enso.interpreter.runtime.scope.ModuleScope;
import java.util.HashMap;
import java.util.Map;
/** A representation of an Atom constructor. */
@ExportLibrary(InteropLibrary.class)
public class AtomConstructor implements TruffleObject {
@ -48,6 +52,7 @@ public class AtomConstructor implements TruffleObject {
public AtomConstructor initializeFields(ArgumentDefinition... args) {
CompilerDirectives.transferToInterpreterAndInvalidate();
this.constructorFunction = buildConstructorFunction(args);
generateMethods(args);
if (args.length == 0) {
cachedInstance = new Atom(this);
} else {
@ -75,6 +80,23 @@ public class AtomConstructor implements TruffleObject {
callTarget, null, new FunctionSchema(FunctionSchema.CallStrategy.ALWAYS_DIRECT, args));
}
private void generateMethods(ArgumentDefinition[] args) {
for (ArgumentDefinition arg : args) {
definitionScope.registerMethod(this, arg.getName(), generateGetter(arg.getPosition()));
}
}
private Function generateGetter(int position) {
GetFieldNode node = new GetFieldNode(null, position);
RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(node);
return new Function(
callTarget,
null,
new FunctionSchema(
FunctionSchema.CallStrategy.ALWAYS_DIRECT,
new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE)));
}
/**
* Gets the name of the constructor.
*

View File

@ -89,8 +89,8 @@ case object DemandAnalysis extends IRPass {
case block @ IR.Expression.Block(expressions, retVal, _, _, _, _) =>
block.copy(
expressions =
expressions.map(x => analyseExpression(x, isInsideCallArgument)),
returnValue = analyseExpression(retVal, isInsideCallArgument)
expressions.map(x => analyseExpression(x, isInsideCallArgument = false)),
returnValue = analyseExpression(retVal, isInsideCallArgument = false)
)
case binding @ IR.Expression.Binding(_, expression, _, _, _) =>
binding.copy(expression =

View File

@ -61,18 +61,24 @@ class DemandAnalysisTest extends CompilerTest {
*
* @return a new inline context
*/
def mkContext: InlineContext = {
def mkInlineContext: InlineContext = {
InlineContext(
localScope = Some(LocalScope.root),
freshNameSupply = Some(new FreshNameSupply)
)
}
def mkModuleContext: ModuleContext = {
ModuleContext(
freshNameSupply = Some(new FreshNameSupply)
)
}
// === The Tests ============================================================
"Suspended arguments" should {
"be forced when assigned" in {
implicit val ctx: InlineContext = mkContext
implicit val ctx: InlineContext = mkInlineContext
val ir =
"""
@ -95,7 +101,7 @@ class DemandAnalysisTest extends CompilerTest {
}
"work correctly when deeply nested" in {
implicit val ctx: InlineContext = mkContext
implicit val ctx: InlineContext = mkInlineContext
val ir =
"""
@ -111,7 +117,7 @@ class DemandAnalysisTest extends CompilerTest {
}
"not be forced when passed to functions" in {
implicit val ctx: InlineContext = mkContext
implicit val ctx: InlineContext = mkInlineContext
val ir =
"""
@ -134,7 +140,7 @@ class DemandAnalysisTest extends CompilerTest {
}
"be forced when used in vector literals" in {
implicit val ctx: InlineContext = mkContext
implicit val ctx: InlineContext = mkInlineContext
val ir =
"""
@ -153,7 +159,7 @@ class DemandAnalysisTest extends CompilerTest {
}
"be marked as not to suspend during codegen when passed to a function" in {
implicit val ctx: InlineContext = mkContext
implicit val ctx: InlineContext = mkInlineContext
val ir =
"""
@ -177,7 +183,7 @@ class DemandAnalysisTest extends CompilerTest {
}
"Non-suspended arguments" should {
implicit val ctx: InlineContext = mkContext
implicit val ctx: InlineContext = mkInlineContext
val ir =
"""x -> y ->
@ -225,7 +231,7 @@ class DemandAnalysisTest extends CompilerTest {
"Suspended blocks" should {
"be forced when used" in {
implicit val ctx: InlineContext = mkContext
implicit val ctx: InlineContext = mkInlineContext
val ir =
"""
@ -250,7 +256,7 @@ class DemandAnalysisTest extends CompilerTest {
}
"not be forced when passed to a function" in {
implicit val ctx: InlineContext = mkContext
implicit val ctx: InlineContext = mkInlineContext
val ir =
"""
@ -272,7 +278,7 @@ class DemandAnalysisTest extends CompilerTest {
}
"be marked as not to suspend during codegen when passed to a function" in {
implicit val ctx: InlineContext = mkContext
implicit val ctx: InlineContext = mkInlineContext
val ir =
"""
@ -292,5 +298,32 @@ class DemandAnalysisTest extends CompilerTest {
.asInstanceOf[IR.CallArgument.Specified]
.shouldBeSuspended shouldEqual Some(false)
}
"force terms in blocks passed directly as arguments" in {
implicit val ctx: ModuleContext = mkModuleContext
val ir =
"""
|bar ~x = foo <|
| x
|""".stripMargin.preprocessModule.analyse
val barFunc = ir.bindings.head
.asInstanceOf[IR.Module.Scope.Definition.Method.Explicit]
val oprCall = barFunc.body
.asInstanceOf[IR.Function.Lambda]
.body
.asInstanceOf[IR.Application.Prefix]
oprCall.function.asInstanceOf[IR.Name].name shouldEqual "<|"
oprCall.arguments.length shouldEqual 2
val xArg = oprCall.arguments(1).asInstanceOf[IR.CallArgument.Specified]
xArg.value shouldBe an[IR.Expression.Block]
xArg.value
.asInstanceOf[IR.Expression.Block]
.returnValue shouldBe an[IR.Application.Force]
}
}
}

View File

@ -23,6 +23,7 @@ object Assoc {
def of(op: String): Assoc =
if (isApplicative(op)) Assoc.Left
else if (op == "in") Assoc.Left
else if (op == "<|") Assoc.Right
else if (op.foldLeft(0)(_ + charAssoc(_)) >= 0) Assoc.Left
else Assoc.Right
}

View File

@ -16,9 +16,10 @@ object Prec {
List("&"),
List("\\"),
List("?"),
List("|>", "<|", ">>", "<<"),
List("<*", "<*>", "*>", "<$", "<$>", "$>", "<+", "<+>", "+>"),
List("<", ">"),
List(","),
List("==", ">", "<", ">=", "<="),
List("+", "-"),
List("*", "/", "%"),
List("^"),

View File

@ -1,4 +1,5 @@
import Base.Bench_Utils
import Base.List
type Counter
type Sum
@ -32,9 +33,9 @@ sum_state = sum_to ->
main =
hundred_mil = 100000000
IO.println "Measuring SumTCO"
Bench_Utils.measure (_ -> here.sum_tco hundred_mil) "sum_tco" 100 20
Bench_Utils.measure (here.sum_tco hundred_mil) "sum_tco" 100 10
IO.println "Measuring State"
Bench_Utils.measure (_ -> here.sum_state hundred_mil) "sum_state" 100 20
Bench_Utils.measure (here.sum_state hundred_mil) "sum_state" 100 10
IO.println "Measuring Co-State"
Bench_Utils.measure (_ -> here.sum_co_state hundred_mil) "sum_co_state" 100 20
Bench_Utils.measure (here.sum_co_state hundred_mil) "sum_co_state" 100 10
IO.println "Bye."

5
test/Test/package.yaml Normal file
View File

@ -0,0 +1,5 @@
name: Test
version: 0.0.1
license: MIT
author: enso-dev@enso.org
maintainer: enso-dev@enso.org

View File

@ -0,0 +1,31 @@
import Base.Test
import Base.List
spec = describe "List" <|
l = Cons 1 <| Cons 2 <| Cons 3 <| Nil
it "should have properly defined length" <|
l.length.should_equal 3
it "should have well defined length when empty" <|
Nil.length.should_equal 0
it "should allow mapping a function over its elements with .map" <|
l.map +1 . head . should_equal 2
it "should allow reversing with .reverse" <|
l.reverse.head.should_equal 3
it "should allow executing an action for each element with .each" <|
sum = State.run Number 0 <|
l.each el->
s = State.get Number
State.put Number s+el
State.get Number
sum.should_equal 6
it "should allow folding the list with an arbitrary operation with .fold" <|
sum = l.fold 0 (+)
prod = l.fold 1 (*)
sum.should_equal 6
prod.should_equal 6
it "should allow checking if an element satisfies a predicate with .any" <|
any_even = l.any (x -> x % 2 == 0)
any_eq_five = l.any (== 5)
any_even.should_be_true
any_eq_five.should_be_false

7
test/Test/src/Main.enso Normal file
View File

@ -0,0 +1,7 @@
import Base.Test
import Test.List_Spec
import Test.Number_Spec
main = Suite.run <|
List_Spec.spec
Number_Spec.spec

View File

@ -0,0 +1,7 @@
import Base.Test
spec = describe "Number" <|
it "should define addition" <|
2+3 . should_equal 5
it "should define multiplication" <|
2*3 . should_equal 6