• Stars
    star
    130
  • Rank 277,575 (Top 6 %)
  • Language
    OCaml
  • License
    MIT License
  • Created almost 9 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Cram like framework for OCaml

expect-test - a cram like framework for OCaml

Introduction

Expect-test is a framework for writing tests in OCaml, similar to Cram. Expect-tests mimic the existing inline tests framework with the let%expect_test construct. The body of an expect-test can contain output-generating code, interleaved with %expect extension expressions to denote the expected output.

When run, these tests will pass iff the output matches what was expected. If a test fails, a corrected file with the suffix โ€œ.correctedโ€ will be produced with the actual output, and the inline_tests_runner will output a diff.

Here is an example Expect-test program, say in foo.ml

open Core

let%expect_test "addition" =
  printf "%d" (1 + 2);
  [%expect {| 4 |}]

When the test is run (as part of inline_tests_runner), foo.ml.corrected will be produced with the contents:

open Core

let%expect_test "addition" =
  printf "%d" (1 + 2);
  [%expect {| 3 |}]

inline_tests_runner will also output the diff:

---foo.ml
+++foo.ml.corrected
File "foo.ml", line 5, characters 0-1:
  open Core

  let%expect_test "addition" =
    printf "%d" (1 + 2);
-|  [%expect {| 4 |}]
+|  [%expect {| 3 |}]

Diffs will be shown in color if the -use-color flag is passed to the test runner executable.

Expects reached from multiple places

A [%expect] can exist in a way that it is encountered multiple times, e.g. in a functor or a function:

let%expect_test _ =
  let f output =
    print_string output;
    [%expect {| hello world |}]
  in
  f "hello world";
  f "hello world";
;;

The [%expect] should capture the exact same output (i.e. up to string equality) at every invocation. In particular, this does **not** work:

let%expect_test _ =
  let f output =
    print_string output;
    [%expect {| \(foo\|bar\) (regexp) |}]
  in
  f "foo";
  f "bar";
;;

Output matching

Matching is done on a line-by-line basis. If any output line fails to match its expected output, the expected line is replaced with the actual line in the final output.

Whitespace

Inside %expect nodes, whitespace around patterns are ignored, and the user is free to put any amount for formatting purposes. The same goes for the actual output.

Ignoring surrounding whitespace allows to write nicely formatted expectation and focus only on matching the bits that matter.

To do this, ppx_expect strips patterns and outputs by taking the smallest rectangle of text that contains the non-whitespace material. All end of line whitespace are ignored as well. So for instance all these lines are equivalent:

  print blah;
  [%expect {|
abc
defg
  hij|}]

  print blah;
  [%expect {|
                abc
                defg
                  hij
  |}]

  print blah;
  [%expect {|
    abc
    defg
      hij
  |}]

However, the last one is nicer to read.

For the rare cases where one does care about what the exact output is, ppx_expect provides the %expect_exact extension point, which only succeed when the untouched output is exactly equal to the untouched pattern.

When producing a correction, ppx_expect tries to respect as much as possible the formatting of the pattern.

Output capture

The extension point [%expect.output] returns a string with the output that would have been matched had an [%expect] node been there instead.

An idiom for testing non-deterministic output is to capture the output using [%expect.output] and either post-process it or inspect it manually, e.g.,

show_process ();
let pid_and_exit_status = [%expect.output] in
let exit_status = discard_pid pid_and_exit_status in
print_endline exit_status;
[%expect {| 1 |}]

This is preferred over output patterns (see below).

Integration with Async, Lwt or other cooperative libraries

If you are writing expect tests for a system using Async, Lwt or any other libraries for cooperative threading, you need some preparation so that everything works well. For instance, you probably need to flush some stdout channel. The expect test runtime takes care of flushing Stdlib.stdout but it doesnโ€™t know about Async.Writer.stdout, Lwt_io.stdout or anything else.

To deal with this, expect\_test provides some hooks in the form of a configuration module Expect_test_config. The default module in scope define no-op hooks that the user can override. Async redefines this module so when Async is opened you can write async-aware expect test.

In addition to Async.Expect_test_config, there is an alternative, Async.Expect_test_config_with_unit_expect. That is easier to use than Async.Expect_test_config because [%expect] has type unit rather than unit Deferred.t. So one can write:

[%expect foo];

rather than:

let%bind () = [%expect foo] in

Expect_test_config_with_unit_expect arrived in 2019-06. We hope to transition from Expect_test_config to Expect_test_config_with_unit_expect, eventually renaming the latter as the former.

LWT

This is what you would need to write expect tests with Lwt:

module Lwt_io_run = struct
  type 'a t = 'a Lwt.t
end

module Lwt_io_flush = struct
  type 'a t = 'a Lwt.t
  let return x = Lwt.return x
  let bind x ~f = Lwt.bind x f
  let to_run x = x
end

module Expect_test_config :
  Expect_test_config_types.S
    with module IO_run = Lwt_io_run
     and module IO_flush = Lwt_io_flush = struct
  module IO_run = Lwt_io_run
  module IO_flush = Lwt_io_flush
  let run x = Lwt_main.run (x ())
  let upon_unreleasable_issue = `CR
end

Comparing Expect-test and unit testing (e.g. let%test_unit)

The simple example above can be easily represented as a unit test:

let%test_unit "addition" = [%test_result: int] (1 + 2) ~expect:4

So, why would one use Expect-test rather than a unit test? There are several differences between the two approaches.

With a unit test, one must write code that explicitly checks that the actual behavior agrees with the expected behavior. %test_result is often a convenient way of doing that, but even using that requires:

  • creating a value to compare
  • writing the type of that value
  • having a comparison function on the value
  • writing down the expected value

With Expect-test, we can simply add print statements whose output gives insight into the behavior of the program, and blank %expect attributes to collect the output. We then run the program to see if the output is acceptable, and if so, replace the original program with its output. E.g we might first write our program like this:

let%expect_test _ =
  printf "%d" (1 + 2);
  [%expect {||}]

The corrected file would contain:

let%expect_test _ =
  printf "%d" (1 + 2);
  [%expect {| 3 |}]

With Expect-test, we only have to write code that prints things that we care about. We donโ€™t have to construct expected values or write code to compare them. We get comparison for free by using diff on the output. And a good diff (e.g. patdiff) can make understanding differences between large outputs substantially easier, much easier than typical unit-testing code that simply states that two values arenโ€™t equal.

Once an Expect-test program produces the desired expected output and we have replaced the original program with its output, we now automatically have a regression test going forward. Any undesired change to the output will lead to a mismatch between the source program and its output.

With Expect-test, the source program and its output are interleaved. This makes debugging easier, because we do not have to jump between source and its output and try to line them up. Furthermore, when there is a mismatch, we can simply add print statements to the source program and run it again. This gives us interleaved source and output with the debug messages interleaved in the right place. We might even insert additional empty %%expect attributes to collect debug messages.

Implementation

Every %expect node in an Expect-test program becomes a point at which the program output is captured. Once the program terminates, the captured outputs are matched against the expected outputs, and interleaved with the original source code to produce the corrected file. Trailing output is appended in a new %expect node.

Build system integration

Follow the same rules as for ppx_inline_test. Just make sure to include ppx_expect.evaluator as a dependency of the test runner. The Jane Street tests contains a few working examples using oasis.

More Repositories

1

magic-trace

magic-trace collects and displays high-resolution traces of what a process is doing
OCaml
4,420
star
2

core

Jane Street Capital's standard library overlay
OCaml
1,030
star
3

base

Standard library for OCaml
OCaml
849
star
4

incremental

A library for incremental computations
OCaml
797
star
5

hardcaml

Hardcaml is an OCaml library for designing hardware.
OCaml
558
star
6

learn-ocaml-workshop

Exercises and projects for Jane Street's OCaml Workshop
OCaml
460
star
7

incr_dom

A library for building dynamic webapps, using Js_of_ocaml.
OCaml
360
star
8

bonsai

A library for building dynamic webapps, using Js_of_ocaml
OCaml
340
star
9

ecaml

Writing Emacs plugin in OCaml
OCaml
242
star
10

core_kernel

Jane Street's standard library overlay (kernel)
OCaml
216
star
11

patdiff

File Diff using the Patience Diff algorithm. https://opensource.janestreet.com/patdiff/
OCaml
199
star
12

async

Jane Street Capital's asynchronous execution library
OCaml
183
star
13

iron

Jane Street code review system
149
star
14

vcaml

OCaml bindings for the Neovim API
OCaml
148
star
15

sexplib

Automated S-expression conversion
OCaml
142
star
16

ppx_inline_test

Syntax extension for writing in-line tests in ocaml code
OCaml
124
star
17

ppx_let

Monadic let-bindings
OCaml
108
star
18

pythonlib

A library to help writing wrappers around ocaml code for python
OCaml
94
star
19

install-ocaml

Instructions for setting up an OCaml development environment
OCaml
94
star
20

jenga

Build system
89
star
21

ppx_sexp_conv

Generation of S-expression conversion functions from type definitions
OCaml
78
star
22

torch

OCaml
74
star
23

bin_prot

Binary protocol generator
OCaml
68
star
24

ppx_optcomp

Optional compilation for OCaml
OCaml
62
star
25

memtrace

Streaming client for OCaml's Memprof
OCaml
61
star
26

ocaml_plugin

Automatically build and dynlink ocaml source files
OCaml
60
star
27

ppx_yojson_conv

[@@deriving] plugin to generate Yojson conversion functions
OCaml
58
star
28

async_kernel

Jane Street Capital's asynchronous execution library (core)
OCaml
57
star
29

ppx_fields_conv

Generation of accessor and iteration functions for ocaml records
OCaml
55
star
30

accessor

A library that makes it nicer to work with nested functional data structures
OCaml
51
star
31

opam-repository

Opam repository for the development version of Jane Street packages
51
star
32

virtual_dom

OCaml bindings for the virtual-dom library
OCaml
51
star
33

spawn

Spawning sub-processes
C
48
star
34

rpc_parallel

Type-safe library for building parallel applications, built on top of Async's Rpc module.
OCaml
47
star
35

incremental_kernel

Library for incremental computations depending only on Core_kernel
OCaml
45
star
36

core_bench

Micro-benchmarking library for OCaml
OCaml
44
star
37

sexp

S-expression swiss knife
OCaml
43
star
38

async_smtp

SMTP client and server
OCaml
42
star
39

ppx_variants_conv

Generation of accessor and iteration functions for ocaml variant types
OCaml
40
star
40

re2

OCaml bindings for RE2
C++
39
star
41

higher_kinded

A library with an encoding of higher kinded types in OCaml
OCaml
36
star
42

async_parallel

Distributed computing library
OCaml
35
star
43

ppx_python

[@@deriving] plugin to generate Python conversion functions
OCaml
33
star
44

stdio

Standard IO Library for OCaml
OCaml
33
star
45

parsexp

S-expression parsing library
OCaml
32
star
46

core_extended

Jane Street Capital's standard library overlay
OCaml
32
star
47

async_unix

Jane Street Capital's asynchronous execution library (unix)
OCaml
30
star
48

ocaml_intrinsics

Provides functions to invoke amd64 instructions (such as clz,popcnt,rdtsc,rdpmc) when available, or compatible software implementation on other targets.
OCaml
29
star
49

ppx_string

ppx extension for string interpolation
OCaml
28
star
50

memtrace_viewer

Interactive memory profiler based on Memtrace
OCaml
27
star
51

async_ssl

Async wrappers for ssl
OCaml
27
star
52

incr_map

Helpers for incremental operations on map like data structures.
OCaml
26
star
53

noise-wireguard-ocaml

An implementation of the Noise Protocol, intended to be used as the base for a Wireguard implementation in OCaml.
OCaml
26
star
54

ppx_enumerate

Generate a list containing all values of a finite type
OCaml
24
star
55

ppx_jane

Standard Jane Street ppx rewriters
Makefile
23
star
56

zstandard

OCaml bindings to Zstandard
OCaml
23
star
57

tracing

Tracing library
OCaml
23
star
58

typerep

Runtime types for OCaml (beta version)
OCaml
22
star
59

camlp4-to-ppx

Convert from camlp4 + syntax extensions to regular OCaml + extension points and attributes
OCaml
22
star
60

postgres_async

OCaml/async implementation of the postgres protocol (i.e., does not use C-bindings to libpq)
OCaml
22
star
61

sexp_pretty

S-expression pretty-printer
OCaml
22
star
62

ppx_custom_printf

Printf-style format-strings for user-defined string conversion
OCaml
21
star
63

ppx_compare

Generation of comparison functions from types
OCaml
21
star
64

zarith_stubs_js

Javascripts stubs for the Zarith library
OCaml
21
star
65

fieldslib

OCaml record fields as first class values
Makefile
20
star
66

patience_diff

Tool and library implementing patience diff
OCaml
20
star
67

lwt-async

Lwt with async backend
OCaml
19
star
68

bigdecimal

Arbitrary-precision decimal based on Zarith
OCaml
19
star
69

configurator

Helper library for gathering system configuration
OCaml
19
star
70

ppx_csv_conv

Generate functions to read/write records in csv format
OCaml
19
star
71

janestreet.github.com

Front page
HTML
19
star
72

textutils

OCaml
18
star
73

ocaml-compiler-libs

compiler libraries repackaged
OCaml
18
star
74

universe

Jane Street universe
OCaml
18
star
75

jsonaf

A library for parsing, manipulating, and serializing data structured as JSON.
OCaml
18
star
76

sexplib0

Library containing the definition of S-expressions and some base converters
OCaml
17
star
77

ppx_stable

Stable types conversions generator
OCaml
17
star
78

ppx_css

A ppx that takes in css strings and produces a module for accessing the unique names defined within.
OCaml
16
star
79

core_unix

Unix-specific portions of Core
OCaml
15
star
80

async_extra

Jane Street Capital's asynchronous execution library (extra)
OCaml
15
star
81

hardcaml_of_verilog

Convert Verilog to a Hardcaml design
OCaml
15
star
82

base_quickcheck

Randomized testing framework, designed for compatibility with Base
OCaml
15
star
83

ppx_type_directed_value

Get [@@deriving]-style generation of type-directed values without writing a ppx
OCaml
15
star
84

async_websocket

A library that implements the websocket protocol on top of Async
OCaml
15
star
85

hardcaml_circuits

Hardcaml Circuits
OCaml
14
star
86

redis-async

Redis client for Async applications
OCaml
14
star
87

ppx_hash

A ppx rewriter that generates hash functions from type expressions and definitions
OCaml
14
star
88

file_path

A library for typed manipulation of UNIX-style file paths.
OCaml
14
star
89

merlin-jst

Merlin with support for Jane Street extensions
OCaml
14
star
90

ppx_assert

Assert-like extension nodes that raise useful errors on failure
OCaml
14
star
91

ppx_js_style

Code style checker for Jane Street Packages
OCaml
14
star
92

core_profiler

Profiling library
OCaml
14
star
93

result

Compat result type
OCaml
14
star
94

async_js

A small library that provide Async support for JavaScript platforms
OCaml
13
star
95

topological_sort

Topological sort algorithm
OCaml
13
star
96

hardcaml_verify

Hardcaml Verification Tools
OCaml
13
star
97

ppx_log

Ppx_sexp_message-like extension nodes for lazily rendering log messages
OCaml
13
star
98

toplevel_expect_test

Toplevel expectation test
OCaml
13
star
99

line-up-words

a small cmd line tool to align words in a sequence of lines in a smart way
OCaml
13
star
100

timezone

Time-zone handling
OCaml
12
star