/
test-amountProperties.js
114 lines (104 loc) · 3.58 KB
/
test-amountProperties.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import { test } from '@agoric/swingset-vat/tools/prepare-test-env-ava.js';
import { makeCopyBag } from '@agoric/store';
import fc from 'fast-check';
import { AmountMath as m, AssetKind } from '../../src/index.js';
import { mockBrand } from './mathHelpers/mockBrand.js';
// Perhaps makeCopyBag should coalesce duplicate labels, but for now, it does
// not.
const distinctLabels = pairs =>
new Set(pairs.map(([label, _qty]) => label)).size === pairs.length;
const positiveCounts = pairs =>
pairs.filter(([_l, qty]) => qty > 0n).length === pairs.length;
const arbBagContents = fc
.nat(7)
.chain(size =>
fc.array(
fc.tuple(fc.string(), fc.bigUint({ max: 1_000_000_000_000_000n })),
{ minLength: size, maxLength: size },
),
)
.filter(pairs => distinctLabels(pairs) && positiveCounts(pairs));
const arbAmount = arbBagContents.map(contents =>
m.make(mockBrand, harden(makeCopyBag(contents))),
);
// Note: we write P => Q as !P || Q since JS has no logical => operator
const implies = (p, q) => !p || q;
test('isEqual is a (total) equivalence relation', async t => {
await fc.assert(
fc.property(
fc.record({ x: arbAmount, y: arbAmount, z: arbAmount }),
({ x, y, z }) => {
return (
// Total
t.true([true, false].includes(m.isEqual(x, y))) &&
// Reflexive
t.true(m.isEqual(x, x)) &&
// Symmetric
t.true(implies(m.isEqual(x, y), m.isEqual(y, x))) &&
// Transitive
t.true(implies(m.isEqual(x, y) && m.isEqual(y, z), m.isEqual(x, z)))
);
},
),
);
});
test('isGTE is a partial order with empty as minimum', async t => {
const empty = m.makeEmpty(mockBrand, AssetKind.COPY_BAG);
await fc.assert(
fc.property(fc.record({ x: arbAmount, y: arbAmount }), ({ x, y }) => {
return (
t.true(m.isGTE(x, empty)) &&
// Total
t.true([true, false].includes(m.isGTE(x, y))) &&
// Reflexive
t.true(m.isGTE(x, x)) &&
// Antisymmetric
t.true(implies(m.isGTE(x, y) && m.isGTE(y, x), m.isEqual(x, y)))
);
}),
);
});
test('add: closed, commutative, associative, monotonic, with empty identity', async t => {
const empty = m.makeEmpty(mockBrand, AssetKind.COPY_BAG);
await fc.assert(
fc.property(
fc.record({ x: arbAmount, y: arbAmount, z: arbAmount }),
({ x, y, z }) => {
return (
// note: + for SET is not total.
t.truthy(m.coerce(mockBrand, m.add(x, y))) &&
// Identity (right)
t.true(m.isEqual(m.add(x, empty), x)) &&
// Identity (left)
t.true(m.isEqual(m.add(empty, x), x)) &&
// Commutative
t.true(m.isEqual(m.add(x, y), m.add(y, x))) &&
// Associative
t.true(m.isEqual(m.add(m.add(x, y), z), m.add(x, m.add(y, z)))) &&
// Monotonic (left)
t.true(m.isGTE(m.add(x, y), x)) &&
// Monotonic (right)
t.true(m.isGTE(m.add(x, y), y))
);
},
),
);
});
test('subtract: (x + y) - y = x; (y - x) + x = y if y >= x', async t => {
await fc.assert(
fc.property(fc.record({ x: arbAmount, y: arbAmount }), ({ x, y }) => {
return (
t.true(m.isEqual(m.subtract(m.add(x, y), y), x)) &&
t.true(m.isGTE(y, x) ? m.isEqual(m.add(m.subtract(y, x), x), y) : true)
);
}),
);
});
test('minmax', t => {
fc.assert(
fc.property(fc.record({ x: arbAmount, y: arbAmount }), ({ x, y }) => {
t.deepEqual(m.min(x, y), m.isGTE(x, y) ? y : x, 'SET: min');
t.deepEqual(m.max(x, y), m.isGTE(x, y) ? x : y, 'SET: max');
}),
);
});