/
time_matcher.ex
127 lines (98 loc) · 3.93 KB
/
time_matcher.ex
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
115
116
117
118
119
120
121
122
123
124
125
126
127
defmodule Machete.TimeMatcher do
@moduledoc """
Defines a matcher that matches Time values
"""
import Machete.Mismatch
defstruct precision: nil, exactly: nil, roughly: nil, before: nil, after: nil
@typedoc """
Describes an instance of this matcher
"""
@opaque t :: %__MODULE__{}
@typedoc """
Describes the arguments that can be passed to this matcher
"""
@type opts :: [
{:precision, 0..6},
{:exactly, Time.t()},
{:roughly, Time.t() | :now},
{:before, Time.t() | :now},
{:after, Time.t() | :now}
]
@doc """
Matches against Time values
Takes the following arguments:
* `precision`: Requires the matched Time to have the specified microsecond precision
* `exactly`: Requires the matched Time to be exactly equal to the specified Time
* `roughly`: Requires the matched Time to be within +/- 10 seconds of the specified Time. The
atom `:now` can be used to use the current time as the specified Time
* `before`: Requires the matched Time to be before or equal to the specified Time. The atom
`:now` can be used to use the current time as the specified Time
* `after`: Requires the matched Time to be after or equal to the specified Time. The atom `:now`
can be used to use the current time as the specified Time
Examples:
iex> assert Time.utc_now() ~> time()
true
iex> assert Time.utc_now() ~> time(precision: 6)
true
iex> assert ~T[00:00:00.000000] ~> time(exactly: ~T[00:00:00.000000])
true
iex> assert Time.utc_now() ~> time(roughly: :now)
true
iex> assert ~T[00:00:00.000000] ~> time(roughly: ~T[00:00:05.000000])
true
iex> assert ~T[00:00:00.000000] ~> time(before: :now)
true
iex> assert ~T[00:00:00.000000] ~> time(before: ~T[00:00:01.000000])
true
iex> assert ~T[23:59:59.999999] ~> time(after: :now)
true
iex> assert ~T[00:00:01.000000] ~> time(after: ~T[00:00:00.000000])
true
"""
@spec time(opts()) :: t()
def time(opts \\ []), do: struct!(__MODULE__, opts)
defimpl Machete.Matchable do
def mismatches(%@for{} = a, b) do
with nil <- matches_type(b),
nil <- matches_precision(b, a.precision),
nil <- matches_exactly(b, a.exactly),
nil <- matches_roughly(b, a.roughly),
nil <- matches_before(b, a.before),
nil <- matches_after(b, a.after) do
end
end
defp matches_type(%Time{}), do: nil
defp matches_type(b), do: mismatch("#{inspect(b)} is not a Time")
defp matches_precision(_, nil), do: nil
defp matches_precision(%Time{microsecond: {_, precision}}, precision), do: nil
defp matches_precision(%Time{microsecond: {_, b_precision}} = b, precision),
do: mismatch("#{inspect(b)} has precision #{b_precision}, expected #{precision}")
defp matches_exactly(_, nil), do: nil
defp matches_exactly(b, exactly) do
if Time.diff(b, exactly, :microsecond) != 0 do
mismatch("#{inspect(b)} is not equal to #{inspect(exactly)}")
end
end
defp matches_roughly(_, nil), do: nil
defp matches_roughly(b, :now), do: matches_roughly(b, Time.utc_now())
defp matches_roughly(b, roughly) do
if Time.diff(b, roughly, :microsecond) not in -10_000_000..10_000_000 do
mismatch("#{inspect(b)} is not within 10 seconds of #{inspect(roughly)}")
end
end
defp matches_before(_, nil), do: nil
defp matches_before(b, :now), do: matches_before(b, Time.utc_now())
defp matches_before(b, before) do
if Time.compare(b, before) != :lt do
mismatch("#{inspect(b)} is not before #{inspect(before)}")
end
end
defp matches_after(_, nil), do: nil
defp matches_after(b, :now), do: matches_after(b, Time.utc_now())
defp matches_after(b, after_var) do
if Time.compare(b, after_var) != :gt do
mismatch("#{inspect(b)} is not after #{inspect(after_var)}")
end
end
end
end