-
Notifications
You must be signed in to change notification settings - Fork 0
/
README.Rmd
179 lines (150 loc) · 5.18 KB
/
README.Rmd
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
---
title: "gentest -- R package for generative property-based testing with automatic doc generation"
output: github_document
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
options(width=200)
library(magrittr)
genHTML <- function(filename) {
readLines(filename) %T>%
gsub('\\tab \\tab \\tab','\\tab',.,fixed=TRUE) %>%
gsub('\\tab \\tab \\tab','\\tab',.,fixed=TRUE) %>%
gsub('\\tab \\tab',"",.,fixed=TRUE) %>%
cat(file=filename, sep='\n')
capture.output(filename %>% tools::Rd2HTML(stdout())) %>%
sub('<link rel="stylesheet" type="text/css" href="R.css" />',"",.,fixed=TRUE) %>%
c('\\ \n', 'See the help file below (as rendered by GitHub):\n',
'- - -', ., '- - -', '\\ \n') %>%
cat(sep='\n')
invisible(file.remove(filename))
}
```
## # API
#### ## Testing
- `gentest` -- evaluates a given function for all the combinations of
the arguments provided as vectors/lists; the results are presented
in a data frame (including possible errors and warnings)
#### ## Auto-Documentation
- `docgen` -- automatically generates the .Rd doc/help file from
the `gentest`'s result; it helps keeping your documentation
up-to-date and in sync with the test results
- `Name` -- use it to tag the return values of your own generator function
in a human-readable way, useful when `docgen` is used
#### ## Built-in generators of atomic vectors
- `Bool`
- `SingleCharacter`
- `String`
- `NumericInRange`
- `IntegerInRange`
#### ## Modifiers
- `Scalar` -- randomly selects a random element from a vector
(i.e. making the length of a vector == 1).
- `withNA` -- injects a fraction of `NA`s into a vector
- `withZero` -- injects a fraction of zeros (0) into a vector
(ensuring that zeros are included in the tests)
#### ## Wrappers
- `List` -- a wrapper for lists
- `DataFrame` -- a wrapper for data frames
- `%|%` -- a binary operator to combine different generators in a list;
to be read as "or"
- `AsOne` -- the vector wrapped by `AsOne` is used as one argument value in
an evaluations instead of element-by-element (as scalars)
- `Replicate` -- produces a vector (list) of values generated by a given
generator or expression multiple times repeatedly
- `Anything` -- a convenience wrapper around a few most typical
generators
#### ## "Safety brakes"
Terminators if something goes wrong in tests:
- `stopIfErr`
- `stopIfWarn`
- `stopIfErrOrWarn`
## # Examples
```{r}
library(gentest)
# A trivial example to demonstrate
# the data frame generated by `gentest`
res1 <- gentest(paste,
x = c('a','b','c'),
y = 1:2,
z = list(100.5, 'Zzzzz'),
sep = '_')
res1
```
```{r}
library(magrittr) # for the pipe operator: %>%
tagNA <- function(x) # Helper function used below
if (x %>% is.na)
x %>% gentest:::addClass('NA') else x
# Assume we want to test the function
# `mean(x, na.rm)` paying attention to the
# combinations of arguments when NA is returned:
res2 <- gentest(mean,
tagNA, # this function is applied to the value returned by `mean`
x = NumericInRange(-1e6,1e6) %|%
String(string_length=3) %|% # max 3 chars to fit the table when printed below
(NumericInRange(-1e6,1e6) %>% withNA),
na.rm = T %|% F)
# Now lets' generate file 'mean.Rd' in sub-directory 'man'
# of the current working directory:
docgen(res2, 'Type-checked mean()')
```
```{r, results='asis', echo=FALSE, warning=FALSE}
genHTML('man/mean.Rd')
```
## # Using your own generator with `docgen`
A generator can be any function. If you want to use the result
of the `gentest` to create automatically the .Rd doc/help file,
it's best (though not mandatory) to tag the return
value of your generator with a human-readable name
through the function `Name`:
```{r}
# Assume you have a generator function that
# generates one of the 3 characters:
# either A or B or C
A_or_B_or_C <- function()
c('A','B','C') %>%
# add a human-readable name via Name()
# if you want to use that generator in
# `docgen`
Name('A or B or C')
res3 <- gentest(paste0,
# `arg1` could be also specified as:
# 'A' %|% 'B' %|% 'C'
arg1 = A_or_B_or_C(),
arg2 = NumericInRange(1,10))
docgen(res3)
```
```{r, results='asis', echo=FALSE, warning=FALSE}
genHTML('man/paste0.Rd')
```
`docgen` generates the documentation only for those
combinations of arguments that do **not** return an error:
```{r}
res4 <- gentest(`+`,
x = 1:3,
y = list(101,102,'abc'))
res4
# 'character' as one of the possible classes/types for
# argument `y` is NOT included in the doc file:
docgen(res4)
```
```{r, results='asis', echo=FALSE, warning=FALSE}
genHTML('man/+.Rd')
```
```{r}
# More complex (non-atomic) objects (data frames, lists)
# are also supported:
myfun <- function(df)
within(df, {
b <- as.character(a + 1)
y <- !x
})
res5 <- gentest(myfun,
df = Replicate(DataFrame(a = NumericInRange(0, 1e6) %>% withZero,
x = Bool())))
docgen(res5)
```
```{r, results='asis', echo=FALSE, warning=FALSE}
genHTML('man/myfun.Rd')
```