Cram Tests
Now that you have a set of unit tests for sandwich-related logic, you realize it would be nice to have some unit tests for burger-related logic as well. But adding a new command to your test
npm script doesn’t feel like The OCaml Way, so you hop on the Reason Discord chatroom to ask for advice. MonadicFanatic1984 once again comes to your aid and points you toward cram tests.
Cram tests
A cram test is essentially a test that runs a command inside a sandbox, then compares the output with some expected output. Dune has built-in support for cram tests, which means:
- You can specify dependencies for a cram test inside your
dune
file - Cram tests are only run when their dependencies change
- When you update cram tests, causing their output to change, you can easily promote the latest output to become the new expected output
- You can run all tests using a single command that doesn’t need to refer to individual tests
cram
stanza
To add dependencies for your cram tests, you need to add a cram stanza to src/order-confirmation/dune
:
(cram
(deps ./output/src/order-confirmation/SandwichTests.mjs))
(cram
(deps ./output/src/order-confirmation/SandwichTests.mjs))
This configuration tells Dune two things:
- Copy
./output/src/order-confirmation/SandwichTests.mjs
into the sandbox directory - Whenever
SandwichTests.re
(the immediate dependency for./output/src/order-confirmation/SandwichTests.mjs
) changes, rebuild and re-run all the cram tests defined in the same directory as thisdune
file
Note that we cannot use the same path to SandwichTests.mjs
as we would if we were running this from the root directory of our project, because cram tests are run inside a sandbox directory (more on that later).
Add tests.t
file
Cram tests are specified inside .t
files. Add a new file src/order-confirmation/tests.t
:
Sandwich tests
$ node ./output/src/order-confirmation/SandwichTests.mjs
Sandwich tests
$ node ./output/src/order-confirmation/SandwichTests.mjs
Note that this is the same path for SandwichTests.mjs
that we specified inside the cram/deps
field.
To run tests, execute
dune build @runtest
dune build @runtest
Here, @runtest is a built-in alias. The above command builds whatever is needed by the tests and then runs the tests.
Aliases
A Dune alias is a virtual build target that other build targets can attach to. You can refer to aliases by name in build commands (like the one above), to build a specific subset of your build targets. In commands, we need to put @
in front of the alias name to distinguish it from file names. Therefore when we refer to an alias, we put an @
in front, e.g. @runtest
, @melange
, etc.
TIP
Cram tests are automatically attached to the @runtest
alias, and melange.emit
stanzas are automatically attached to the @melange
alias.
Promotion
After running dune build @runtest
, you should be able to see the output from the cram test you just added[1]:
@@ -1,2 +1,27 @@
Sandwich tests
$ node ./output/src/cram-tests/SandwichTests.mjs
+ TAP version 13
+ # Subtest: Item.Sandwich.toEmoji
+ ok 1 - Item.Sandwich.toEmoji
+ ---
+ duration_ms: 2.173649
+ ...
+ # Subtest: Item.Sandwich.toPrice
+ ok 2 - Item.Sandwich.toPrice
+ ---
+ duration_ms: 0.216146
+ ...
+ # Subtest: Item.Sandwich.toPrice returns lower price for Turducken on Tuesdays
+ ok 3 - Item.Sandwich.toPrice returns lower price for Turducken on Tuesdays
+ ---
+ duration_ms: 0.106589
+ ...
+ 1..3
+ # tests 3
+ # suites 0
+ # pass 3
+ # fail 0
+ # cancelled 0
+ # skipped 0
+ # todo 0
+ # duration_ms 0.111087
@@ -1,2 +1,27 @@
Sandwich tests
$ node ./output/src/cram-tests/SandwichTests.mjs
+ TAP version 13
+ # Subtest: Item.Sandwich.toEmoji
+ ok 1 - Item.Sandwich.toEmoji
+ ---
+ duration_ms: 2.173649
+ ...
+ # Subtest: Item.Sandwich.toPrice
+ ok 2 - Item.Sandwich.toPrice
+ ---
+ duration_ms: 0.216146
+ ...
+ # Subtest: Item.Sandwich.toPrice returns lower price for Turducken on Tuesdays
+ ok 3 - Item.Sandwich.toPrice returns lower price for Turducken on Tuesdays
+ ---
+ duration_ms: 0.106589
+ ...
+ 1..3
+ # tests 3
+ # suites 0
+ # pass 3
+ # fail 0
+ # cancelled 0
+ # skipped 0
+ # todo 0
+ # duration_ms 0.111087
However, this doesn’t count as a passing cram test, because this output can’t be compared to any expected output. Dune has a nice feature that lets you promote the latest output of a cram test to become the expected output:
dune promote
dune promote
The above command updates your src/order-confirmation/tests.t
cram test file, which now looks something like this:
Sandwich tests
$ node ./output/src/order-confirmation/SandwichTests.mjs
TAP version 13
# Subtest: Item.Sandwich.toEmoji
ok 1 - Item.Sandwich.toEmoji
---
duration_ms: 1.821041
...
# Subtest: Item.Sandwich.toPrice
ok 2 - Item.Sandwich.toPrice
---
duration_ms: 0.731916
...
# Subtest: Item.Sandwich.toPrice returns lower price for Turducken on Tuesdays
ok 3 - Item.Sandwich.toPrice returns lower price for Turducken on Tuesdays
---
duration_ms: 0.086875
...
1..3
# tests 3
# suites 0
# pass 3
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 0.081042
Sandwich tests
$ node ./output/src/order-confirmation/SandwichTests.mjs
TAP version 13
# Subtest: Item.Sandwich.toEmoji
ok 1 - Item.Sandwich.toEmoji
---
duration_ms: 1.821041
...
# Subtest: Item.Sandwich.toPrice
ok 2 - Item.Sandwich.toPrice
---
duration_ms: 0.731916
...
# Subtest: Item.Sandwich.toPrice returns lower price for Turducken on Tuesdays
ok 3 - Item.Sandwich.toPrice returns lower price for Turducken on Tuesdays
---
duration_ms: 0.086875
...
1..3
# tests 3
# suites 0
# pass 3
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 0.081042
The part underneath the command is the expected output of the command.
Add npm
scripts
Instead of typing dune
commands over and over, let’s add the relevant npm
scripts to your package.json
file. Replace the current test
script with these three scripts:
"test": "npm run build -- @runtest",
"test:watch": "npm run build -- @runtest --watch",
"promote": "npm run dune -- promote"
"test": "npm run build -- @runtest",
"test:watch": "npm run build -- @runtest --watch",
"promote": "npm run dune -- promote"
Since JSON doesn’t support comments, you can optionally add some lines to the scriptsComments
section of package.json
to explain what each command does:
"test": "# Run the tests",
"test:watch": "# Watch files and re-run tests",
"promote": "# Promote most recent output to expected output"
"test": "# Run the tests",
"test:watch": "# Watch files and re-run tests",
"promote": "# Promote most recent output to expected output"
Run npm run test
to check that it works.
Sanitize cram test output
The cram test still doesn’t succeed, but now it’s because the most recent output and the expected output don’t match:
@@ -4,17 +4,17 @@ Sandwich tests
# Subtest: Item.Sandwich.toEmoji
ok 1 - Item.Sandwich.toEmoji
---
- duration_ms: 2.225448
+ duration_ms: 3.012631
...
# Subtest: Item.Sandwich.toPrice
ok 2 - Item.Sandwich.toPrice
---
- duration_ms: 0.262691
+ duration_ms: 0.276333
...
# Subtest: Item.Sandwich.toPrice returns lower price for Turducken on Tuesdays
ok 3 - Item.Sandwich.toPrice returns lower price for Turducken on Tuesdays
---
- duration_ms: 0.109477
+ duration_ms: 0.140118
...
1..3
# tests 3
@@ -24,4 +24,4 @@ Sandwich tests
# cancelled 0
# skipped 0
# todo 0
- # duration_ms 0.125331
+ # duration_ms 0.143172
@@ -4,17 +4,17 @@ Sandwich tests
# Subtest: Item.Sandwich.toEmoji
ok 1 - Item.Sandwich.toEmoji
---
- duration_ms: 2.225448
+ duration_ms: 3.012631
...
# Subtest: Item.Sandwich.toPrice
ok 2 - Item.Sandwich.toPrice
---
- duration_ms: 0.262691
+ duration_ms: 0.276333
...
# Subtest: Item.Sandwich.toPrice returns lower price for Turducken on Tuesdays
ok 3 - Item.Sandwich.toPrice returns lower price for Turducken on Tuesdays
---
- duration_ms: 0.109477
+ duration_ms: 0.140118
...
1..3
# tests 3
@@ -24,4 +24,4 @@ Sandwich tests
# cancelled 0
# skipped 0
# todo 0
- # duration_ms 0.125331
+ # duration_ms 0.143172
The problem is that the recorded durations for individual unit tests is a little bit different on each test run. Therefore we must sanitize the test output, i.e. remove the parts that are non-deterministic. Update your cram test command:
$ node ./output/src/order-confirmation/SandwichTests.mjs | sed '/duration_ms/d'
$ node ./output/src/order-confirmation/SandwichTests.mjs | sed '/duration_ms/d'
Now the output of the command is piped to sed '/duration_ms/d'
, which removes all the lines containing the string duration_ms
.
Follow these steps to achieve your first successful cram test run:
- Run
npm run test:watch
- It shows errors because the expected output still contains the
duration_ms
lines - Run
npm run promote
in another terminal to promote the latest output to the expected output - You should see
Success, waiting for filesystem changes...
No test output means it succeeded. Somewhat unintuitively, when cram tests succeed, they remain silent![2]
Sandbox
All cram tests execute in a sandbox. What goes into the sandbox directory is determined by the value of the cram/deps
field in your dune
file. Let’s write an explorative cram test that allows us to peek inside the sandbox. First, install the tree-node-cli package:
npm install --save-dev tree-node-cli
npm install --save-dev tree-node-cli
Then add a new cram test to src/order-confirmation/tests.t
:
See all the files inside the sandbox
$ npx tree
See all the files inside the sandbox
$ npx tree
If npm run test:watch
is still running, you’ll immediately see some output like this:
@@ -24,3 +24,8 @@ Sandwich tests
See all the files inside the sandbox
$ npx tree
+ order-confirmation
+ └── output
+ └── src
+ └── order-confirmation
+ └── SandwichTests.mjs
@@ -24,3 +24,8 @@ Sandwich tests
See all the files inside the sandbox
$ npx tree
+ order-confirmation
+ └── output
+ └── src
+ └── order-confirmation
+ └── SandwichTests.mjs
The sandbox directory only contains a single file output/src/order-confirmation/SandwichTests.mjs
. Add another explorative cram test:
Show detail of SandwichTests.mjs
$ ls -la ./output/src/order-confirmation/SandwichTests.mjs
Show detail of SandwichTests.mjs
$ ls -la ./output/src/order-confirmation/SandwichTests.mjs
The output should now look like this:
@@ -24,6 +24,12 @@ Sandwich tests
See all the files inside the sandbox
$ npx tree
+ cram-tests
+ └── output
+ └── src
+ └── cram-tests
+ └── SandwichTests.mjs
Show detail of SandwichTests.mjs
$ ls -la ./output/src/order-confirmation/SandwichTests.mjs
+ lrwxr-xr-x 1 fhsu staff 86 Mar 16 22:09 ./output/src/order-confirmation/SandwichTests.mjs -> ../../../../../../../../default/src/order-confirmation/output/src/order-confirmation/SandwichTests.mjs
@@ -24,6 +24,12 @@ Sandwich tests
See all the files inside the sandbox
$ npx tree
+ cram-tests
+ └── output
+ └── src
+ └── cram-tests
+ └── SandwichTests.mjs
Show detail of SandwichTests.mjs
$ ls -la ./output/src/order-confirmation/SandwichTests.mjs
+ lrwxr-xr-x 1 fhsu staff 86 Mar 16 22:09 ./output/src/order-confirmation/SandwichTests.mjs -> ../../../../../../../../default/src/order-confirmation/output/src/order-confirmation/SandwichTests.mjs
From this, you can see that ./output/src/order-confirmation/SandwichTests.mjs
is actually a symbolic link which links to the real file inside your build directory, specifically this file:
_build/default/src/order-confirmation/output/src/order-confirmation/SandwichTests.mjs
_build/default/src/order-confirmation/output/src/order-confirmation/SandwichTests.mjs
SandwichTests.mjs
being a symbolic link explains why you can run the test successfully even when the rest of the .mjs
files aren’t present in the sandbox directory.
Better dependencies
There’s a problem with the cram test dependencies. The cram test is re-run if SandwichTests.re
changes, but if Item.re
(which SandwichTests.re
depends on), were to change, it wouldn’t trigger a test re-run. To see this, make the following change to Item.Sandwich.toEmoji
:
let toEmoji = t =>
Printf.sprintf(
{js|🥪(%s)|js},
switch (t) {
| Portabello => {js|🍄|js}
| Ham => {js|🐷|js}
| Unicorn => {js|🦄|js}
| Turducken => {js|🦃🦆🐓|js}
| Turducken => {js|🦃🦆🐓💩|js}
},
);
let toEmoji = t =>
Printf.sprintf(
{js|🥪(%s)|js},
switch (t) {
| Portabello => {js|🍄|js}
| Ham => {js|🐷|js}
| Unicorn => {js|🦄|js}
| Turducken => {js|🦃🦆🐓|js}
| Turducken => {js|🦃🦆🐓💩|js}
},
);
In the terminal in which npm run test:watch
is running, you would expect to see a failing test, but no errors appear. You have to add (alias melange)
to cram/deps
:
(cram
(deps
(alias melange)
./output/src/order-confirmation/SandwichTests.mjs))
(cram
(deps
(alias melange)
./output/src/order-confirmation/SandwichTests.mjs))
Now the test finally fails. The presence of (alias melange)
means that all build targets attached to the @melange
alias are now dependencies for the cram tests in this directory.
expand_aliases_in_sandbox
stanza
If you kept the $ npx tree
cram test around, you’ll observe that even though all build targets attached to @melange
are dependencies, they aren’t copied into the sandbox directory. You can change that behavior by adding the expand_aliases_in_sandbox stanza to your dune-project
file:
; Copy all build targets for an alias into the sandbox
(expand_aliases_in_sandbox)
; Copy all build targets for an alias into the sandbox
(expand_aliases_in_sandbox)
The npx tree
cram test makes it clear that a lot of files have been copied over to the sandbox directory. Since SandwichTests.mjs
is a @melange
build target too, it is redundant and can be removed from cram/deps
:
(cram
(deps
(alias melange)))
(cram
(deps
(alias melange)))
Fix the bug in Item.re
. The SandwichTests
cram test should pass once more.
WARNING
Using expand_aliases_in_sandbox
is very convenient, but it may noticeably impact cram test performance. If you feel your cram tests are too slow, you should remove it and go back to putting .mjs
files in your cram/deps
field.
Fantastico! You’ve set up Dune cram tests to streamline the testing process. In the next chapter, we’ll add logic for promotional discounts.
Overview
- A cram test runs a command inside a sandbox and compares the output of that command with some expected output
- Dune has extensive support for cram tests, including the ability to promote the latest output to become the expected output
- A
cram
stanza is used to specify dependencies for your cram tests - Cram test files have the
.t
extension - Aliases are virtual build targets that other build targets can be attached to
- Cram tests that produce nondeterministic output must have their output be sanitized
- Cram tests execute in a sandbox
- For Melange projects,
(alias melange)
is generally the best dependency for your cram tests - The
expand_aliases_in_sandbox
stanza allows you to avoid having to put generated.mjs
files in your cram test dependencies, at the cost of having slower cram tests
Exercises
1. Add new source file src/order-confirmation/BurgerTests.re
:
open Fest;
test("A fully-loaded burger", () =>
expect
|> equal(
Item.Burger.toEmoji({
lettuce: true,
onions: 2,
cheese: 3,
tomatoes: true,
bacon: 4,
}),
{js|🍔|js},
)
);
open Fest;
test("A fully-loaded burger", () =>
expect
|> equal(
Item.Burger.toEmoji({
lettuce: true,
onions: 2,
cheese: 3,
tomatoes: true,
bacon: 4,
}),
{js|🍔|js},
)
);
Fix the broken logic inside the test and add a new cram test for BurgerTests
in src/order-confirmation/tests.t
.
Solution
Fixed test:
test("A fully-loaded burger", () =>
expect
|> equal(
Item.Burger.toEmoji({
lettuce: true,
onions: 2,
cheese: 3,
tomatoes: true,
bacon: 4,
}),
{js|🍔{🥬,🍅,🧅×2,🧀×3,🥓×4}|js},
)
);
test("A fully-loaded burger", () =>
expect
|> equal(
Item.Burger.toEmoji({
lettuce: true,
onions: 2,
cheese: 3,
tomatoes: true,
bacon: 4,
}),
{js|🍔{🥬,🍅,🧅×2,🧀×3,🥓×4}|js},
)
);
New cram test:
Burger tests
$ node ./output/src/order-confirmation/BurgerTests.mjs | sed '/duration_ms/d'
TAP version 13
# Subtest: A fully-loaded burger
ok 1 - A fully-loaded burger
---
...
1..1
# tests 1
# suites 0
# pass 1
# fail 0
# cancelled 0
# skipped 0
# todo 0
Burger tests
$ node ./output/src/order-confirmation/BurgerTests.mjs | sed '/duration_ms/d'
TAP version 13
# Subtest: A fully-loaded burger
ok 1 - A fully-loaded burger
---
...
1..1
# tests 1
# suites 0
# pass 1
# fail 0
# cancelled 0
# skipped 0
# todo 0
2. Add three more tests to BurgerTests
module:
test("Burger with 0 of onions, cheese, or bacon doesn't show those emoji", () =>
expect |> equal(Item.Burger.toEmoji(/* burger record */), {js|🍔|js})
);
test(
"Burger with 1 of onions, cheese, or bacon should show just the emoji without ×",
() =>
expect |> equal(Item.Burger.toEmoji(/* burger record */), {js|🍔|js})
);
test("Burger with 2 or more of onions, cheese, or bacon should show ×", () =>
expect |> equal(Item.Burger.toEmoji(/* burger record */), {js|🍔|js})
);
test("Burger with 0 of onions, cheese, or bacon doesn't show those emoji", () =>
expect |> equal(Item.Burger.toEmoji(/* burger record */), {js|🍔|js})
);
test(
"Burger with 1 of onions, cheese, or bacon should show just the emoji without ×",
() =>
expect |> equal(Item.Burger.toEmoji(/* burger record */), {js|🍔|js})
);
test("Burger with 2 or more of onions, cheese, or bacon should show ×", () =>
expect |> equal(Item.Burger.toEmoji(/* burger record */), {js|🍔|js})
);
Get the tests to pass and promote the new test output.
Solution
Fixed tests:
test("Burger with 0 of onions, cheese, or bacon doesn't show those emoji", () =>
expect
|> equal(
Item.Burger.toEmoji({
lettuce: true,
tomatoes: true,
onions: 0,
cheese: 0,
bacon: 0,
}),
{js|🍔{🥬,🍅}|js},
)
);
test(
"Burger with 1 of onions, cheese, or bacon should show just the emoji without ×",
() =>
expect
|> equal(
Item.Burger.toEmoji({
lettuce: true,
tomatoes: true,
onions: 1,
cheese: 1,
bacon: 1,
}),
{js|🍔{🥬,🍅,🧅,🧀,🥓}|js},
)
);
test("Burger with 2 or more of onions, cheese, or bacon should show ×", () =>
expect
|> equal(
Item.Burger.toEmoji({
lettuce: true,
tomatoes: true,
onions: 2,
cheese: 2,
bacon: 2,
}),
{js|🍔{🥬,🍅,🧅×2,🧀×2,🥓×2}|js},
)
);
test("Burger with 0 of onions, cheese, or bacon doesn't show those emoji", () =>
expect
|> equal(
Item.Burger.toEmoji({
lettuce: true,
tomatoes: true,
onions: 0,
cheese: 0,
bacon: 0,
}),
{js|🍔{🥬,🍅}|js},
)
);
test(
"Burger with 1 of onions, cheese, or bacon should show just the emoji without ×",
() =>
expect
|> equal(
Item.Burger.toEmoji({
lettuce: true,
tomatoes: true,
onions: 1,
cheese: 1,
bacon: 1,
}),
{js|🍔{🥬,🍅,🧅,🧀,🥓}|js},
)
);
test("Burger with 2 or more of onions, cheese, or bacon should show ×", () =>
expect
|> equal(
Item.Burger.toEmoji({
lettuce: true,
tomatoes: true,
onions: 2,
cheese: 2,
bacon: 2,
}),
{js|🍔{🥬,🍅,🧅×2,🧀×2,🥓×2}|js},
)
);
3. Recently, some Cafe Emoji customers have gotten into the habit of ordering burgers with an absurd number of toppings. You can barely even hold these monstrous burgers in your hands, and on the small chance that you manage to grab one, it will explode as soon as you bite into it. Madame Jellobutter has therefore decided that whenever someone orders a burger with more than 12 toppings, it will be served in a big bowl. Add the following test to BurgerTests
:
test("Burger with more than 12 toppings should also show bowl emoji", () => {
expect
|> equal(
Item.Burger.toEmoji({
lettuce: true,
tomatoes: true,
onions: 4,
cheese: 2,
bacon: 5,
}),
{js|🍔🥣{🥬,🍅,🧅×4,🧀×2,🥓×5}|js},
);
expect
|> equal(
Item.Burger.toEmoji({
lettuce: true,
tomatoes: true,
onions: 4,
cheese: 2,
bacon: 4,
}),
{js|🍔{🥬,🍅,🧅×4,🧀×2,🥓×4}|js},
);
});
test("Burger with more than 12 toppings should also show bowl emoji", () => {
expect
|> equal(
Item.Burger.toEmoji({
lettuce: true,
tomatoes: true,
onions: 4,
cheese: 2,
bacon: 5,
}),
{js|🍔🥣{🥬,🍅,🧅×4,🧀×2,🥓×5}|js},
);
expect
|> equal(
Item.Burger.toEmoji({
lettuce: true,
tomatoes: true,
onions: 4,
cheese: 2,
bacon: 4,
}),
{js|🍔{🥬,🍅,🧅×4,🧀×2,🥓×4}|js},
);
});
Now update Item.Burger.toEmoji
to make that test pass.
Hint
Note that lettuce
and tomatoes
each count as 1 topping.
Solution
New version of Item.Burger.toEmoji
:
let toEmoji = t => {
let multiple = (emoji, count) =>
switch (count) {
| 0 => ""
| 1 => emoji
| count => Printf.sprintf({js|%s×%d|js}, emoji, count)
};
switch (t) {
| {lettuce: false, onions: 0, cheese: 0, tomatoes: false, bacon: 0} => {js|🍔|js}
| {lettuce, onions, cheese, tomatoes, bacon} =>
let toppingsCount =
(lettuce ? 1 : 0) + (tomatoes ? 1 : 0) + onions + cheese + bacon;
Printf.sprintf(
{js|🍔%s{%s}|js},
toppingsCount > 12 ? {js|🥣|js} : "",
[|
lettuce ? {js|🥬|js} : "",
tomatoes ? {js|🍅|js} : "",
multiple({js|🧅|js}, onions),
multiple({js|🧀|js}, cheese),
multiple({js|🥓|js}, bacon),
|]
|> Js.Array.filter(~f=str => str != "")
|> Js.Array.join(~sep=","),
);
};
let toEmoji = t => {
let multiple = (emoji, count) =>
switch (count) {
| 0 => ""
| 1 => emoji
| count => Printf.sprintf({js|%s×%d|js}, emoji, count)
};
switch (t) {
| {lettuce: false, onions: 0, cheese: 0, tomatoes: false, bacon: 0} => {js|🍔|js}
| {lettuce, onions, cheese, tomatoes, bacon} =>
let toppingsCount =
(lettuce ? 1 : 0) + (tomatoes ? 1 : 0) + onions + cheese + bacon;
Printf.sprintf(
{js|🍔%s{%s}|js},
toppingsCount > 12 ? {js|🥣|js} : "",
[|
lettuce ? {js|🥬|js} : "",
tomatoes ? {js|🍅|js} : "",
multiple({js|🧅|js}, onions),
multiple({js|🧀|js}, cheese),
multiple({js|🥓|js}, bacon),
|]
|> Js.Array.filter(~f=str => str != "")
|> Js.Array.join(~sep=","),
);
};
View source code and demo for this chapter.
You might notice that the output from Node test runner looks different when run inside a cram test. That’s because it uses different test reporters depending on whether you run it directly or through another process. ↩︎
A successful cram test won’t print any output from the test, but you may see some logging that indicates that build targets were produced, e.g.
shell↩︎refmt src/order-confirmation/Item.re.ml ppx src/order-confirmation/Item.re.pp.ml ocamldep src/order-confirmation/.output.mobjs/melange__Item.impl.d melc src/order-confirmation/.output.mobjs/melange/melange__Item.{cmi,cmj,cmt} melc src/order-confirmation/output/src/order-confirmation/Item.mjs melc src/order-confirmation/.output.mobjs/melange/melange__SandwichTests.{cmi,cmj,cmt} melc src/order-confirmation/.output.mobjs/melange/melange__Order.{cmi,cmj,cmt} melc src/order-confirmation/.output.mobjs/melange/melange__Index.{cmi,cmj,cmt}
refmt src/order-confirmation/Item.re.ml ppx src/order-confirmation/Item.re.pp.ml ocamldep src/order-confirmation/.output.mobjs/melange__Item.impl.d melc src/order-confirmation/.output.mobjs/melange/melange__Item.{cmi,cmj,cmt} melc src/order-confirmation/output/src/order-confirmation/Item.mjs melc src/order-confirmation/.output.mobjs/melange/melange__SandwichTests.{cmi,cmj,cmt} melc src/order-confirmation/.output.mobjs/melange/melange__Order.{cmi,cmj,cmt} melc src/order-confirmation/.output.mobjs/melange/melange__Index.{cmi,cmj,cmt}