File: qassert.R

package info (click to toggle)
r-cran-checkmate 2.3.4-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 1,512 kB
  • sloc: ansic: 2,211; sh: 9; makefile: 8
file content (134 lines) | stat: -rw-r--r-- 5,148 bytes parent folder | download
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
#' @title Quick argument checks on (builtin) R types
#'
#' @description
#' The provided functions parse rules which allow to express some of the most
#' frequent argument checks by typing just a few letters.
#'
#' @param x [\code{any}]\cr
#'  Object the check.
#' @param rules [\code{character}]\cr
#'   Set of rules. See details.
#' @template var.name
#' @return
#'  \code{qassert} throws an \code{R} exception if object \code{x} does
#'  not comply to at least one of the \code{rules} and returns the tested object invisibly
#'  otherwise.
#'  \code{qtest} behaves the same way but returns \code{FALSE} if none of the
#'  \code{rules} comply.
#'  \code{qexpect} is intended to be inside the unit test framework \code{\link[testthat]{testthat}} and
#'  returns an \code{\link[testthat]{expectation}}.
#'
#' @details
#' The rule is specified in up to three parts.
#' \enumerate{
#'  \item{
#'    Class and missingness check.
#'    The first letter is an abbreviation for the class. If it is
#'    provided uppercase, missing values are prohibited.
#'    Supported abbreviations:
#'    \tabular{rl}{
#'      \code{[bB]} \tab Bool / logical.\cr
#'      \code{[iI]} \tab Integer.\cr
#'      \code{[xX]} \tab Integerish (numeric convertible to integer, see \code{\link{checkIntegerish}}).\cr
#'      \code{[rR]} \tab Real / double.\cr
#'      \code{[cC]} \tab Complex.\cr
#'      \code{[nN]} \tab Numeric (integer or double).\cr
#'      \code{[sS]} \tab String / character.\cr
#'      \code{[fF]} \tab Factor\cr
#'      \code{[aA]} \tab Atomic.\cr
#'      \code{[vV]} \tab Atomic vector (see \code{\link{checkAtomicVector}}).\cr
#'      \code{[lL]} \tab List. Missingness is defined as \code{NULL} element.\cr
#'      \code{[mM]} \tab Matrix.\cr
#'      \code{[dD]} \tab Data.frame. Missingness is checked recursively on columns.\cr
#'      \code{[pP]} \tab POSIXct date.\cr
#'      \code{[e]}  \tab Environment.\cr
#'      \code{[0]}  \tab \code{NULL}.\cr
#'      \code{[*]}  \tab placeholder to allow any type.
#'    }
#'    Note that the check for missingness does not distinguish between
#'    \code{NaN} and \code{NA}. Infinite values are not treated as missing, but
#'    can be caught using boundary checks (part 3).
#'    }
#'  \item{
#'    Length definition. This can be one of
#'    \tabular{rl}{
#'      \code{[*]} \tab any length,\cr
#'      \code{[?]} \tab length of zero or one,\cr
#'      \code{[+]} \tab length of at least one, or\cr
#'      \code{[0-9]+} \tab exact length specified as integer.
#'    }
#'    Preceding the exact length with one of the comparison operators \code{=}/\code{==},
#'    \code{<}, \code{<=}, \code{>=} or \code{>} is also supported.
#'  }
#'  \item{
#'    Range check as two numbers separated by a comma, enclosed by square brackets
#'    (endpoint included) or parentheses (endpoint excluded).
#'    For example, \dQuote{[0, 3)} results in \code{all(x >= 0 & x < 3)}.
#'    The lower and upper bound may be omitted which is the equivalent of a negative or
#'    positive infinite bound, respectively.
#'    By definition \code{[0,]} contains \code{Inf}, while \code{[0,)} does not.
#'    The same holds for the left (lower) boundary and \code{-Inf}.
#'    E.g., the rule \dQuote{N1()} checks for a single finite numeric which is not NA,
#'    while \dQuote{N1[)} allows \code{-Inf}.
#'  }
#' }
#' @note
#' The functions are inspired by the blog post of Bogumił Kamiński:
#' \url{http://rsnippets.blogspot.de/2013/06/testing-function-agruments-in-gnu-r.html}.
#' The implementation is mostly written in C to minimize the overhead.
#' @seealso \code{\link{qtestr}} and \code{\link{qassertr}} for efficient checks
#' of list elements and data frame columns.
#' @useDynLib checkmate c_qassert
#' @export
#' @examples
#' # logical of length 1
#' qtest(NA, "b1")
#'
#' # logical of length 1, NA not allowed
#' qtest(NA, "B1")
#'
#' # logical of length 0 or 1, NA not allowed
#' qtest(TRUE, "B?")
#'
#' # numeric with length > 0
#' qtest(runif(10), "n+")
#'
#' # integer with length > 0, NAs not allowed, all integers >= 0 and < Inf
#' qtest(1:3, "I+[0,)")
#'
#' # either an emtpy list or a character vector with <=5 elements
#' qtest(1, c("l0", "s<=5"))
#'
#' # data frame with at least one column and no missing value in any column
#' qtest(iris, "D+")
qassert = function(x, rules, .var.name = vname(x)) {
  res = .Call(c_qassert, x, rules, FALSE)
  if (!isTRUE(res))
    mstop(qmsg(res, .var.name), call. = sys.call(-1L))
  invisible(x)
}

#' @useDynLib checkmate c_qtest
#' @rdname qassert
#' @export
qtest = function(x, rules) {
  .Call(c_qtest, x, rules, FALSE, 1L)
}

#' @useDynLib checkmate c_qassert
#' @template expect
#' @rdname qassert
#' @include makeExpectation.R
#' @export
qexpect = function(x, rules, info = NULL, label = vname(x)) {
  res = .Call(c_qassert, x, rules, FALSE)
  if (!isTRUE(res))
    res = qmsg(res, label)
  makeExpectation(x, res, info = info, label = label)
}

qmsg = function(msg, vname) {
  if (length(msg) > 1L)
    msg = paste0(c("One of the following must apply:", strwrap(msg, prefix = " * ")), collapse = "\n")
  sprintf("Assertion on '%s' failed. %s.", vname, msg)
}