-
-
Notifications
You must be signed in to change notification settings - Fork 415
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add PonyCheck to standard library (#4034)
Closes #4029
- Loading branch information
1 parent
a75350f
commit f9b837e
Showing
22 changed files
with
5,281 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
## Add PonyCheck to the standard library | ||
|
||
PonyCheck, a property based testing library, has been added to the standard library. PonyCheck was previously its own project but has been merged into the standard library. | ||
|
||
For the most part existing PonyCheck tests will continue to work once you make a few minor changes. | ||
|
||
### Remove PonyCheck from your corral.json | ||
|
||
As PonyCheck is now part of the standard library, you don't need to use `corral` to fetch it. | ||
|
||
### Change the package name | ||
|
||
Previously, the PonyCheck package was called `ponycheck`. To conform with standard library naming conventions, the package has been renamed to `pony_check`. So anywhere you previously had: | ||
|
||
```pony | ||
use "ponycheck" | ||
``` | ||
|
||
You'll need to update to: | ||
|
||
```pony | ||
use "pony_check" | ||
``` | ||
|
||
### Update the PonyCheck primitive name | ||
|
||
If you were using the `Ponycheck` primitive, you'll need to change it to its new name `PonyCheck`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pony_check |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
# pony_check | ||
|
||
A program showing example tests using the PonyCheck property based testing package. | ||
|
||
## How to compile | ||
|
||
With a minimal Pony installation, in the same directory as this README file, run `ponyc`. You should see content building the necessary packages, which ends with: | ||
|
||
```console | ||
... | ||
Generating | ||
Reachability | ||
Selector painting | ||
Data prototypes | ||
Data types | ||
Function prototypes | ||
Functions | ||
Descriptors | ||
Optimising | ||
Writing ./pony_check.o | ||
Linking ./pony_check | ||
``` | ||
|
||
## How to Run | ||
|
||
Once `pony_check` has been compiled, in the same directory as this README file, run `./pony_check`. You should see a PonyTest runner output showing the tests run and their results; just like you would with PonyTest in general, except the tests in question are PonyCheck tests. | ||
|
||
```console | ||
1 test started, 0 complete: list/reverse/one started | ||
2 tests started, 0 complete: list/properties started | ||
3 tests started, 0 complete: list/reverse started | ||
4 tests started, 0 complete: custom_class/map started | ||
5 tests started, 0 complete: custom_class/custom_generator started | ||
6 tests started, 0 complete: async/tcp_sender started | ||
7 tests started, 0 complete: collections/operation_on_random_collection_elements started | ||
8 tests started, 0 complete: custom_class/flat_map started | ||
8 tests started, 1 complete: list/properties complete | ||
8 tests started, 2 complete: custom_class/flat_map complete | ||
8 tests started, 3 complete: custom_class/custom_generator complete | ||
8 tests started, 4 complete: custom_class/map complete | ||
8 tests started, 5 complete: list/reverse/one complete | ||
8 tests started, 6 complete: collections/operation_on_random_collection_elements complete | ||
8 tests started, 7 complete: list/reverse complete | ||
8 tests started, 8 complete: async/tcp_sender complete | ||
---- Passed: list/reverse | ||
---- Passed: list/reverse/one | ||
---- Passed: list/properties | ||
---- Passed: custom_class/flat_map | ||
---- Passed: custom_class/map | ||
---- Passed: custom_class/custom_generator | ||
---- Passed: async/tcp_sender | ||
---- Passed: collections/operation_on_random_collection_elements | ||
---- | ||
---- 8 tests ran. | ||
---- Passed: 8 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
use "itertools" | ||
use "net" | ||
use "pony_check" | ||
use "ponytest" | ||
|
||
class _TCPSenderConnectionNotify is TCPConnectionNotify | ||
let _message: String | ||
|
||
new create(message: String) => | ||
_message = message | ||
|
||
fun ref connected(conn: TCPConnection ref) => | ||
conn.write(_message) | ||
|
||
fun ref connect_failed(conn: TCPConnection ref) => | ||
None | ||
|
||
fun ref received( | ||
conn: TCPConnection ref, | ||
data: Array[U8] iso, | ||
times: USize) | ||
: Bool | ||
=> | ||
conn.close() | ||
true | ||
|
||
class val TCPSender | ||
""" | ||
Class under test. | ||
Simple class that sends a string to a TCP server. | ||
""" | ||
|
||
let _auth: AmbientAuth | ||
|
||
new val create(auth: AmbientAuth) => | ||
_auth = auth | ||
|
||
fun send( | ||
host: String, | ||
port: String, | ||
message: String): TCPConnection tag^ | ||
=> | ||
TCPConnection( | ||
_auth, | ||
recover _TCPSenderConnectionNotify(message) end, | ||
host, | ||
port) | ||
|
||
|
||
// Test Cruft | ||
class MyTCPConnectionNotify is TCPConnectionNotify | ||
let _ph: PropertyHelper | ||
let _expected: String | ||
|
||
new create(ph: PropertyHelper, expected: String) => | ||
_ph = ph | ||
_expected = expected | ||
|
||
fun ref received( | ||
conn: TCPConnection ref, | ||
data: Array[U8] iso, | ||
times: USize) | ||
: Bool | ||
=> | ||
_ph.log("received " + data.size().string() + " bytes", true) | ||
// assert we received the expected string | ||
_ph.assert_eq[USize](data.size(), _expected.size()) | ||
for bytes in Iter[U8](_expected.values()).zip[U8]((consume data).values()) do | ||
_ph.assert_eq[U8](bytes._1, bytes._2) | ||
end | ||
// this will signal to the PonyCheck engine that this property is done | ||
// it will nonetheless execute until the end | ||
_ph.complete(true) | ||
conn.close() | ||
true | ||
|
||
fun ref connect_failed(conn: TCPConnection ref) => | ||
_ph.fail("connect failed") | ||
conn.close() | ||
|
||
class MyTCPListenNotify is TCPListenNotify | ||
|
||
let _sender: TCPSender | ||
let _ph: PropertyHelper | ||
let _expected: String | ||
|
||
new create( | ||
sender: TCPSender, | ||
ph: PropertyHelper, | ||
expected: String) => | ||
_sender = sender | ||
_ph = ph | ||
_expected = expected | ||
|
||
|
||
fun ref listening(listen: TCPListener ref) => | ||
let address = listen.local_address() | ||
try | ||
(let host, let port) = address.name(where reversedns = None, servicename = false)? | ||
|
||
// now that we know the server's address we can actually send something | ||
_ph.dispose_when_done( | ||
_sender.send(host, port, _expected)) | ||
else | ||
_ph.fail("could not determine server host and port") | ||
end | ||
|
||
fun ref connected(listen: TCPListener ref): TCPConnectionNotify iso^ => | ||
recover iso | ||
MyTCPConnectionNotify(_ph, _expected) | ||
end | ||
|
||
fun ref not_listening(listen: TCPListener ref) => | ||
_ph.fail("not listening") | ||
|
||
class _AsyncTCPSenderProperty is Property1[String] | ||
fun name(): String => "async/tcp_sender" | ||
|
||
fun params(): PropertyParams => | ||
PropertyParams(where async' = true, timeout' = 5_000_000_000) | ||
|
||
fun gen(): Generator[String] => | ||
Generators.unicode() | ||
|
||
fun ref property(sample: String, ph: PropertyHelper) => | ||
let sender = TCPSender(ph.env.root) | ||
ph.dispose_when_done( | ||
TCPListener( | ||
ph.env.root, | ||
recover MyTCPListenNotify(sender, ph, "PONYCHECK") end, | ||
"127.0.0.1", | ||
"0")) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
/* | ||
This example shows a rather complex scenario. | ||
Here we want to generate both a random size that we use as the length of an array that | ||
we create in our property and a random number of operations on random elements | ||
of the array. | ||
We don't want to use another source of randomness for determining a random element | ||
in our property code, as it is better for reproducability of the property | ||
to make the whole execution only depend on the randomness used when drawing samples | ||
from the Generator. | ||
This way, given a certain seed in the `PropertyParams` we can actually reliably reproduce a failed example. | ||
*/ | ||
use "collections" | ||
use "pony_check" | ||
|
||
class val _OperationOnCollection[T, R = String] is Stringable | ||
"""Represents a certain operation on an element addressed by idx.""" | ||
let idx: USize | ||
let op: {(T): R} val | ||
|
||
new val create(idx': USize, op': {(T): R} val) => | ||
idx = idx' | ||
op = op' | ||
|
||
fun string(): String iso^ => | ||
recover | ||
String.>append("_OperationOnCollection(" + idx.string() + ")") | ||
end | ||
|
||
class _OperationOnCollectionProperty is Property1[(USize, Array[_OperationOnCollection[String]])] | ||
fun name(): String => "collections/operation_on_random_collection_elements" | ||
|
||
fun gen(): Generator[(USize, Array[_OperationOnCollection[String]])] => | ||
""" | ||
This generator produces: | ||
* a tuple of the number of elements of a collection to be created in the property code | ||
* an array of a randomly chosen operation on a random element of the collection | ||
Therefore, we first create a generator for the collection size, | ||
then we `flat_map` over this generator, to generate one for an array of operations, | ||
whose index is randomly chosen from a range of `[0, num_elements)`. | ||
The first generated value determines the further values, we want to construct a generator | ||
based on the value of another one. For this kind of construct, we use `Generator.flat_map`. | ||
We then `flat_map` again over the Generator for the element index (that is bound by `num_elements`) | ||
to get a generator for an `_OperationOnCollection[String]` which needs the index as a constructor argument. | ||
The operation generator depends on the value of the randomly chosen element index, | ||
thus we use `Generator.flat_map` again. | ||
""" | ||
Generators.usize(2, 100).flat_map[(USize, Array[_OperationOnCollection[String]])]( | ||
{(num_elements: USize) => | ||
let elements_generator = | ||
Generators.array_of[_OperationOnCollection[String]]( | ||
Generators.usize(0, num_elements-1) | ||
.flat_map[_OperationOnCollection[String]]({(element) => | ||
Generators.one_of[_OperationOnCollection[String]]( | ||
[ | ||
_OperationOnCollection[String](element, {(s) => recover String.>append(s).>append("foo") end }) | ||
_OperationOnCollection[String](element, {(s) => recover String.>append(s).>append("bar") end }) | ||
] | ||
) | ||
}) | ||
) | ||
Generators.zip2[USize, Array[_OperationOnCollection[String]]]( | ||
Generators.unit[USize](num_elements), | ||
elements_generator) | ||
}) | ||
|
||
fun ref property(sample: (USize, Array[_OperationOnCollection[String]]), h: PropertyHelper) => | ||
(let len, let ops) = sample | ||
|
||
// create and fill the array | ||
let coll = Array[String].create(len) | ||
for i in Range(0, len) do | ||
coll.push(i.string()) | ||
end | ||
|
||
// execute random operations on random elements of the array | ||
for op in ops.values() do | ||
try | ||
let elem = coll(op.idx)? | ||
let res = op.op(elem) | ||
if not h.assert_true(res.contains("foo") or res.contains("bar")) then return end | ||
else | ||
h.fail("illegal access") | ||
end | ||
end | ||
|
Oops, something went wrong.