-
Notifications
You must be signed in to change notification settings - Fork 98
SDK-576 Design: Dfx testing story #354
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
Changes from all commits
ad0cb70
51cea0a
01c5858
7483a22
40c45b2
b137784
6abc559
caa802b
2ad18e0
c3a36d2
10df321
58b05fa
315a202
672ace7
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,31 @@ | ||
| { | ||
| "canisters": { | ||
| "cleanSheets": { | ||
| "_comment:": "we list `main` service code versus its `unit-tests` (each is a single Motoko function to run) versus its `functional-tests` (each is an `install-and-go`), versus `e2e-tests` that each involve more than a single `install-and-go`, and require a `test-script` over several canisters." | ||
|
|
||
| "main": "src/cleanSheets.mo" | ||
| "unit-tests": { | ||
| "all": "test/main.mo" | ||
| }, | ||
| "func-tests": { | ||
| "functionalTest1": "test/simpleAdaptonDivByZero.mo", | ||
| "functionalTest2": "test/simpleExcelEmulation.mo", | ||
| }, | ||
| "e2e-tests": { | ||
| "AliceBob": { | ||
| "_comment:": "test-script installs and scripts bots `alice` and `bob`" | ||
| "test-script": "test/aliceBob/test-script" | ||
| "aliceCanister": "test/aliceBob/aliceCanister.mo" | ||
|
Contributor
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. Are those (already defined canisters) necessary? I see the need for a
Contributor
Author
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. the ideas here are that
So, the
Contributor
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. Are there reasons to scope them? It seems this just causes more copy-paste.
Contributor
Author
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. Separate namespaces. I expect that separate namespaces will generally be more useful than one big shared global one, where you have to "mimic" the separation I want here by introducing long names for everything. In terms of implementation complexity, separate namespaces is no more complex either; there's no implementation-based reason not to support them. In terms of user value: If I have two test scripts that introduce two cansiters called
Contributor
Author
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.
I don't follow this concern.
Contributor
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. Removing some boiler-plate copy-paste; |
||
| "bobCanister": "test/aliceBob/bobCanister.mo" | ||
| } | ||
| "AliceBobCharlie": { | ||
| "_comment:": "reuse bots `alice` and `bob`, and includes bot `charlie`" | ||
| "test-script": "test/aliceBobCharlie/test-script" | ||
| "aliceCanister": "test/aliceBob/aliceCanister.mo" | ||
| "bobCanister": "test/aliceBob/bobCanister.mo" | ||
| "charlieCanister": "test/aliceBobCharlie/charlieCanister.mo" | ||
| } | ||
| } | ||
| }, | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| (OMITTING EXAMPLE CODE) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| (OMITTING EXAMPLE CODE) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| (OMITTING EXAMPLE CODE) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| (EXAMPLE CODE OMITTED) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| (OMITTING EXAMPLE CODE) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| (EXAMPLE CODE OMITTED) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| (EXAMPLE CODE OMITTED) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| install bobCanister | ||
| ==> #ok(bobId) | ||
| install aliceCanister | ||
| ==> #ok(aliceId) | ||
| call bobId addFriend aliceId | ||
|
Contributor
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. With inter-canister calls, could you elaborate on your rationale for using a DSL instead of actual Motoko? In Motoko:
Contributor
Author
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. The issue is scoping. The intention of this Alice canister code is that it does not import (or know about) Bob's code statically, and vice versa. They are "test bots" written independently, and only linked dynamically, by a test script. I realize that this tiny example could mention those names using aliases; but, the point here was to illustrate a multi-canister test that does not use aliases, and still has canisters interact.
Contributor
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. Where does
Contributor
Author
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. Yes, In general, these patterns may have variables, which may appear free in subsequent actions, as illustrated here with
Contributor
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. Ha so |
||
| ==> #ok(()) | ||
| call aliceId addFriend BobId | ||
| ==> #ok(()) | ||
| call bobId collaborateWithAlice | ||
| ==> #ok(()) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| (EXAMPLE CODE OMITTED) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| install bobCanister ==> #ok(bobId) | ||
| install aliceCanister ==> #ok(aliceId) | ||
| install charlieCanister ==> #ok(charlieId) | ||
| # comment out these calls, to try alternatives where Charlie starts all interactions | ||
| # call bobId addFriend aliceId ==> #ok(()) | ||
| # call bobId addFriend charlieId ==> #ok(()) | ||
| # call aliceId addFriend bobId ==> #ok(()) | ||
| # call aliceId addFriend charlieId ==> #ok(()) | ||
| call charlieId addFriend bobId ==> #ok(()) | ||
| call charlieId addFriend aliceId ==> #ok(()) | ||
| call charlieId collaborateWithAliceAndBob ==> #ok(()) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,208 @@ | ||
| import R "mo:stdlib/result"; | ||
| import P "mo:stdlib/prelude"; | ||
|
|
||
| import T "../src/types"; | ||
| import A "../src/adapton"; | ||
| import E "../src/eval"; | ||
|
|
||
| /* | ||
|
|
||
| This file reproduces the intro example from the Adapton Rust docs, | ||
| found here: | ||
|
|
||
| https://docs.rs/adapton/0/adapton/#demand-driven-change-propagation | ||
|
|
||
| The DCG update behavior asserted here (see uses of | ||
| `assertLogEventLast` below) matches the cleaning/dirtying behavior | ||
| described in the link above, and in other documents and papers. | ||
|
|
||
| In particular, the same example is used in a recorded adapton talk, | ||
| from the http://adapton.org website. The link above has slides with | ||
| still pictures from the talk. | ||
|
|
||
| */ | ||
|
|
||
| actor SimpleAdaptonDivByZero { | ||
|
|
||
| public func go() { | ||
| let ctx : T.Adapton.Context = A.init(true); | ||
|
|
||
| // "cell 1 holds 42": | ||
| let cell1 : T.Adapton.NodeId = assertOkPut(A.put(ctx, #nat(1), #nat(42))); | ||
| A.assertLogEventLast | ||
| (ctx, #put(#nat(1), #nat(42), [])); | ||
|
|
||
| // "cell 2 holds 2": | ||
| let cell2 : T.Adapton.NodeId = assertOkPut(A.put(ctx, #nat(2), #nat(2))); | ||
| A.assertLogEventLast | ||
| (ctx, #put(#nat(2), #nat(2), [])); | ||
|
|
||
| // "cell 3 holds a suspended closure for this expression: | ||
| // | ||
| // get(cell1) / get(cell2) | ||
| // | ||
| // ...and it is still unevaluated". | ||
| // | ||
| let cell3 : T.Adapton.NodeId = assertOkPut( | ||
| A.putThunk(ctx, #nat(3), | ||
| E.closure( | ||
| null, | ||
| #strictBinOp(#div, | ||
| #get(#refNode(cell1)), | ||
| #get(#refNode(cell2)) | ||
| ))) | ||
| ); | ||
|
|
||
| // "cell 4 holds a suspended closure for this expression: | ||
| // | ||
| // if (get(cell2) == 0) { 0 } | ||
| // else { get(cell3) } | ||
| // | ||
| // ...and it is still unevaluated". | ||
| // | ||
| let cell4 : T.Adapton.NodeId = assertOkPut( | ||
| A.putThunk(ctx, #nat(4), | ||
| E.closure( | ||
| null, | ||
| #ifCond(#strictBinOp(#eq, | ||
| #get(#refNode(cell2)), | ||
| #nat(0)), | ||
| #nat(0), | ||
| #get(#refNode(cell3))))) | ||
| ); | ||
|
|
||
| // demand division: | ||
| let res1 = assertOkGet(A.get(ctx, cell4)); | ||
| A.assertLogEventLast(ctx, | ||
| #get(#nat(4), #ok(#nat(21)), | ||
| [ | ||
| #evalThunk( | ||
| #nat(4), #ok(#nat(21)), | ||
| [#get(#nat(2), #ok(#nat(2)), []), | ||
| #get(#nat(3), #ok(#nat(21)), | ||
| [ | ||
| #evalThunk( | ||
| #nat(3), #ok(#nat(21)), | ||
| [ | ||
| #get(#nat(1), #ok(#nat(42)), []), | ||
| #get(#nat(2), #ok(#nat(2)), []) | ||
| ]) | ||
| ]) | ||
| ]) | ||
| ]) | ||
| ); | ||
|
|
||
| // "cell 2 holds 0": | ||
| ignore A.put(ctx, #nat(2), #nat(0)); | ||
| A.assertLogEventLast | ||
| (ctx, | ||
| #put(#nat(2), #nat(0), | ||
| [ | ||
| #dirtyIncomingTo( | ||
| #nat(2), | ||
| [ | ||
| #dirtyEdgeFrom( | ||
| #nat(3), | ||
| [ | ||
| #dirtyIncomingTo( | ||
| #nat(3), | ||
| [ | ||
| #dirtyEdgeFrom( | ||
| #nat(4), | ||
| [ | ||
| #dirtyIncomingTo(#nat(4), []) | ||
| ]) | ||
| ]) | ||
| ]), | ||
| #dirtyEdgeFrom( | ||
| #nat(4), | ||
| [ | ||
| #dirtyIncomingTo( | ||
| #nat(4), []) | ||
| ]) | ||
| ]) | ||
| ]) | ||
| ); | ||
|
|
||
| // re-demand division: | ||
| let res2 = assertOkGet(A.get(ctx, cell4)); | ||
| A.assertLogEventLast | ||
| (ctx, | ||
| #get(#nat(4), #ok(#nat(0)), | ||
| [ | ||
| #cleanThunk( | ||
| #nat(4), false, | ||
| [ | ||
| #cleanEdgeTo( | ||
| #nat(2), false, []) | ||
| ]), | ||
| #evalThunk( | ||
| #nat(4), #ok(#nat(0)), | ||
| [ | ||
| #get(#nat(2), #ok(#nat(0)), []) | ||
| ]) | ||
| ])); | ||
|
|
||
| // "cell 2 holds 2": | ||
| ignore A.put(ctx, #nat(2), #nat(2)); | ||
| A.assertLogEventLast | ||
| (ctx, | ||
| #put(#nat(2), #nat(2), | ||
| [ | ||
| #dirtyIncomingTo( | ||
| #nat(2), | ||
| [ | ||
| #dirtyEdgeFrom( | ||
| #nat(4), | ||
| [ | ||
| #dirtyIncomingTo( | ||
| #nat(4), []) | ||
| ]) | ||
| ]) | ||
| ])); | ||
|
|
||
| // re-demand division: | ||
| let res3 = assertOkGet(A.get(ctx, cell4)); | ||
| A.assertLogEventLast | ||
| (ctx, | ||
| #get(#nat(4), #ok(#nat(21)), | ||
| [ | ||
| #cleanThunk( | ||
| #nat(4), false, | ||
| [ | ||
| #cleanEdgeTo(#nat(2), false, []) | ||
| ]), | ||
| #evalThunk( | ||
| #nat(4), #ok(#nat(21)), | ||
| [ | ||
| #get(#nat(2), #ok(#nat(2)), []), | ||
| #get(#nat(3), #ok(#nat(21)), | ||
| [ | ||
| #cleanThunk( | ||
| #nat(3), true, | ||
| [ | ||
| #cleanEdgeTo(#nat(1), true, []), | ||
| #cleanEdgeTo(#nat(2), true, []) | ||
| ]) | ||
| ]) | ||
| ]) | ||
| ])); | ||
| }; | ||
|
|
||
|
|
||
| func assertOkPut(r:R.Result<T.Adapton.NodeId, T.Adapton.PutError>) : T.Adapton.NodeId { | ||
| switch r { | ||
| case (#ok(id)) { id }; | ||
| case _ { P.unreachable() }; | ||
| } | ||
| }; | ||
|
|
||
| func assertOkGet(r:R.Result<T.Adapton.Result, T.Adapton.GetError>) : T.Eval.Result { | ||
| switch r { | ||
| case (#ok(res)) { res }; | ||
| case _ { P.unreachable() }; | ||
| } | ||
| }; | ||
| }; | ||
|
|
||
| //SimpleAdaptonDivByZero.go(); | ||
|
Contributor
Author
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. This is a common class of test: it's a single canister, and a single call (e.g., So to be clear, this class is so common that I don't even want to write a script for any of them. The idea is that mentioning them in I call each a "functional test" since each one is much more than a unit test, but not as complex as a multi-canister test, or one that requires multiple calls. Within one call, we can do a pretty full test of a non-actor library's functionality, as illustrated here. (Thanks for the idea @nikclayton-dfinity!)
Contributor
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. Is that comment load bearing?
Contributor
Author
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. I don't understand the question.
Contributor
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. Has that comment semantic meaning? |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| import R "mo:stdlib/result"; | ||
| import P "mo:stdlib/prelude"; | ||
| import Debug "mo:stdlib/debug"; | ||
|
|
||
| import T "../src/types"; | ||
| import A "../src/adapton"; | ||
| import E "../src/eval"; | ||
|
|
||
| actor simpleExcelEmulation { | ||
|
|
||
| public func go() { | ||
|
|
||
| let sheetExp : T.Eval.Exp = | ||
| #sheet( | ||
| #text("S"), | ||
| [ | ||
| [ #nat(1), #nat(2) ], | ||
| [ #strictBinOp(#add, | ||
| #cellOcc(0,0), | ||
| #cellOcc(0,1)), | ||
| #strictBinOp(#mul, | ||
| #nat(2), | ||
| #cellOcc(1,0)) ] | ||
| ]); | ||
|
|
||
| // Adapton maintains our dependence graph | ||
| let actx : T.Adapton.Context = A.init(true); | ||
|
|
||
| // create the initial Sheet datatype from the DSL expression above | ||
| let s : T.Sheet.Sheet = { | ||
| switch (E.evalExp(actx, null, sheetExp)) { | ||
| case (#ok(#sheet(s))) s; | ||
| case _ { P.unreachable() }; | ||
| }}; | ||
|
|
||
| // Demand that the sheet's results are fully refreshed | ||
| ignore E.Sheet.refresh(actx, s); | ||
| ignore E.Sheet.refresh(actx, s); | ||
| A.assertLogEventLast( | ||
| actx, | ||
| #get(#tagTup(#text("S"), [#nat(1), #nat(1), #text("out")]), #ok(#nat(6)), []) | ||
| ); | ||
|
|
||
| // Update the sheet by overwriting (0,0) with a new formula: | ||
| ignore E.Sheet.update(actx, s, 0, 0, | ||
| #strictBinOp(#add, #nat(666), #cellOcc(0,1))); | ||
|
|
||
| // Demand that the sheet's results are fully refreshed | ||
| ignore E.Sheet.refresh(actx, s); | ||
| ignore E.Sheet.refresh(actx, s); | ||
| assert (s.errors.len() == 0); | ||
| A.assertLogEventLast( | ||
| actx, | ||
| #get(#tagTup(#text("S"), [#nat(1), #nat(1), #text("out")]), #ok(#nat(1_340)), []) | ||
| ); | ||
|
|
||
| // Update the sheet, creating a cycle at (0,0): | ||
| ignore E.Sheet.update(actx, s, 0, 0, #cellOcc(0,0)); | ||
| ignore E.Sheet.refresh(actx, s); | ||
| assert (s.errors.len() != 0); | ||
|
|
||
| // Update the sheet, removing the cycle: | ||
| ignore E.Sheet.update(actx, s, 0, 0, | ||
| #strictBinOp(#add, #nat(666), #cellOcc(0,1))); | ||
|
|
||
| // Demand that the sheet's results are fully refreshed | ||
| ignore E.Sheet.refresh(actx, s); | ||
| ignore E.Sheet.refresh(actx, s); | ||
| assert (s.errors.len() == 0); | ||
| A.assertLogEventLast( | ||
| actx, | ||
| #get(#tagTup(#text("S"), [#nat(1), #nat(1), #text("out")]), #ok(#nat(1_340)), []) | ||
| ); | ||
| }; | ||
| }; | ||
|
|
||
| //simpleExcelEmulation.go() |
Uh oh!
There was an error while loading. Please reload this page.