Skip to content

Commit 6d13994

Browse files
committed
Add unit tests and standardize code style
1 parent 3ba882b commit 6d13994

13 files changed

Lines changed: 226 additions & 85 deletions

R/cstr_foreign_key.R

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,16 @@ cstr_foreign_key <- function(cols, reference) {
2121
ref_table_chr <- ".self"
2222
ref_table_obj <- .table
2323
} else {
24-
if (is.null(attr(.table, "context"))) stop("'reference' refers to a separate
25-
table, but no information on other tables in the dataset was found")
24+
if (is.null(attr(.table, "context")))
25+
stop("'reference' refers to a separate table, but no information on ",
26+
"other tables in the dataset was found")
2627
ref_table_chr <- select_names(
2728
ref_table_quo,
2829
attr(.table, "context")
2930
)
30-
if (length(ref_table_chr) != 1) stop("A foreign key must have a single
31-
reference table, but ", length(ref_table_chr), " were selected")
31+
if (length(ref_table_chr) != 1)
32+
stop("A foreign key must have a single reference table, but ",
33+
length(ref_table_chr), " were selected")
3234
ref_table_obj <- attr(.table, "context")[[ref_table_chr]]
3335
}
3436
ref_cols_chr <- select_names(ref_cols_quo, ref_table_obj)
@@ -48,13 +50,14 @@ cstr_foreign_key <- function(cols, reference) {
4850
#' @export
4951
validate_qf_constraint.cstr_foreign_key <- function(x, table) {
5052
ref_table_obj <- resolve_table_reference(table, x$ref_table)
51-
if (is.null(ref_table_obj)) stop(pretty_class(x), " references a ",
52-
"non-existent table: ", x$ref_table)
53+
if (is.null(ref_table_obj))
54+
stop(pretty_class(x), " references a non-existent table (",
55+
x$ref_table, ")")
5356
references_unique_key <- purrr::some(constraints(ref_table_obj), \(cstr)
5457
inherits(cstr, "cstr_unique_key") && setequal(x$ref_cols, cstr$cols)
5558
)
56-
if (!references_unique_key) stop(pretty_class(x), " does not reference a ",
57-
"unique key")
59+
if (!references_unique_key)
60+
stop(pretty_class(x), " does not reference a unique key")
5861
NextMethod()
5962
}
6063

R/qf_constraint.R

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,17 +205,17 @@ check_constraint_strict <- function(constraint, table) {
205205
#'
206206
#' @export
207207
exceptions <- function(x) {
208-
if (!is_qf_constraint(x)) stop("'x' must be a <qf_constraint>, not ",
209-
typeof(x))
208+
if (!is_qf_constraint(x))
209+
stop("'x' must be a <qf_constraint>, not ", typeof(x))
210210

211211
attr(x, "exceptions")
212212
}
213213

214214
#' @rdname exceptions
215215
#' @export
216216
`exceptions<-` <- function(x, value) {
217-
if (!is_qf_constraint(x)) stop("'x' must be a <qf_constraint>, not ",
218-
typeof(x))
217+
if (!is_qf_constraint(x))
218+
stop("'x' must be a <qf_constraint>, not ", typeof(x))
219219
exception_list <-
220220
if (is_qf_exception(value))
221221
list(value)

R/qf_dataset.R

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ new_qf_dataset <- function(x) {
1212

1313
validate_qf_dataset <- function(x) {
1414
purrr::walk(x, \(table)
15-
if (!is_qf_table(table)) stop("A <qf_dataset> must only contain ",
16-
"<qf_table> objects")
15+
if (!is_qf_table(table))
16+
stop("A <qf_dataset> must only contain <qf_table> objects")
1717
)
1818
purrr::iwalk(x, \(table, name) tryCatch(
1919
validate_qf_table(table),
20-
error = \(e) stop("Table ", name, " has invalid structure: \n",
21-
e$message)
20+
error = \(e) stop(
21+
"Table ", name, " has invalid structure: \n",
22+
e$message
23+
)
2224
))
2325
x
2426
}

R/qf_exception.R

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ except_where <- function(...) {
4646
excepted_rows <- function(exception, table) {
4747
conditions <- purrr::map(exception, rlang::eval_tidy, data = table)
4848
purrr::walk(conditions, \(cond)
49-
if (!rlang::is_logical(cond, n = nrow(table))) stop("Each condition must ",
50-
"evaluate to a logical vector of length ", nrow(table), ", not a ",
51-
typeof(cond), " of length ", length(cond))
49+
if (!rlang::is_logical(cond, n = nrow(table)))
50+
stop("Each condition must evaluate to a logical vector of length ",
51+
nrow(table), ", not a ", typeof(cond), " of length ", length(cond))
5252
)
5353
purrr::reduce(conditions, `&`)
5454
}

R/qf_table.R

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,10 @@ constraints <- function(x) {
151151
#' @rdname constraints
152152
#' @export
153153
`constraints<-` <- function(x, value) {
154-
if (!is_qf_table(x)) stop("'x' must be a qualifyr table, not ", typeof(x))
155-
if (!rlang::is_list(value)) stop("'value' must be a list, not ",
156-
typeof(value))
154+
if (!is_qf_table(x))
155+
stop("'x' must be a qualifyr table, not ", typeof(x))
156+
if (!rlang::is_list(value))
157+
stop("'value' must be a list, not ", typeof(value))
157158
attr(x, "constraints") <- purrr::modify(value, as_qf_constraint, table = x)
158159
validate_qf_table(x)
159160
}
@@ -201,10 +202,10 @@ constraint_index <- function(table, cols = NULL, class) {
201202
)
202203
}
203204
matches <- which(matching_class & matching_cols)
204-
if (length(matches) == 0) stop(deparse(sys.call(-1)), " does not match any
205-
constraints")
206-
else if (length(matches) > 1) stop(deparse(sys.call(-1)), " is ambiguous,
207-
i.e., matches multiple constraints")
205+
if (length(matches) == 0)
206+
stop(deparse(sys.call(-1)), " does not match any constraints")
207+
else if (length(matches) > 1)
208+
stop(deparse(sys.call(-1)), " is ambiguous (matches multiple constraints)")
208209
else matches
209210
}
210211

R/utils.R

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@ resolve_table_reference <- function(table, reference) {
3434
} else {
3535
# even though this is not an exported function, informative error messages
3636
# are given here so they do not need to be repeated everywhere that uses it
37-
if (!is_qf_dataset(attr(table, "context"))) stop("Reference points to a ",
38-
"separate table, but no information on other tables in the dataset was ",
39-
"found")
40-
if (!(reference %in% names(attr(table, "context")))) stop("Reference ",
41-
"points to a non-existent table: ", reference)
37+
if (!is_qf_dataset(attr(table, "context")))
38+
stop("Reference points to a separate table, but no information on other ",
39+
"tables in the dataset was found")
40+
if (!(reference %in% names(attr(table, "context"))))
41+
stop("Reference points to a non-existent table (", reference, ")")
4242
attr(table, "context")[[reference]]
4343
}
4444
}
@@ -54,7 +54,6 @@ resolve_table_reference <- function(table, reference) {
5454
#'
5555
#' @noRd
5656
pretty_class <- function(object) {
57-
if (any(grepl("cnst", class(object)))) browser()
5857
paste0("<", class(object)[[1]], ">")
5958
}
6059

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# print() output is correct
2+
3+
<qf_dataset> with 3 tables:
4+
$ flights : <qf_table>
5+
.. - Data: qf_table [336,776 x 19] (S3: qf_table/tbl_df/tbl/data.frame)
6+
.. .. $ year : int [1:336776] 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 ...
7+
.. .. $ month : int [1:336776] 1 1 1 1 1 1 1 1 1 1 ...
8+
.. .. $ day : int [1:336776] 1 1 1 1 1 1 1 1 1 1 ...
9+
.. .. $ dep_time : int [1:336776] 517 533 542 544 554 554 555 557 557 558 ...
10+
.. .. $ sched_dep_time: int [1:336776] 515 529 540 545 600 558 600 600 600 600 ...
11+
.. .. $ dep_delay : num [1:336776] 2 4 2 -1 -6 -4 -5 -3 -3 -2 ...
12+
.. .. $ arr_time : int [1:336776] 830 850 923 1004 812 740 913 709 838 753 ...
13+
.. .. $ sched_arr_time: int [1:336776] 819 830 850 1022 837 728 854 723 846 745 ...
14+
.. .. $ arr_delay : num [1:336776] 11 20 33 -18 -25 12 19 -14 -8 8 ...
15+
.. .. $ carrier : chr [1:336776] "UA" "UA" "AA" "B6" ...
16+
.. .. $ flight : int [1:336776] 1545 1714 1141 725 461 1696 507 5708 79 301 ...
17+
.. .. $ tailnum : chr [1:336776] "N14228" "N24211" "N619AA" "N804JB" ...
18+
.. .. $ origin : chr [1:336776] "EWR" "LGA" "JFK" "JFK" ...
19+
.. .. $ dest : chr [1:336776] "IAH" "IAH" "MIA" "BQN" ...
20+
.. .. $ air_time : num [1:336776] 227 227 160 183 116 150 158 53 140 138 ...
21+
.. .. $ distance : num [1:336776] 1400 1416 1089 1576 762 ...
22+
.. .. $ hour : num [1:336776] 5 5 5 5 6 5 6 6 6 6 ...
23+
.. .. $ minute : num [1:336776] 15 29 40 45 0 58 0 0 0 0 ...
24+
.. .. $ time_hour : POSIXct[1:336776], format: "2013-01-01 05:00:00" "2013-01-01 05:00:00" ...
25+
.. - Constraints:
26+
.. .. - <cstr_primary_key> [year, month, day, carrier, flight]
27+
.. .. - <cstr_foreign_key> [carrier] => airlines[carrier]
28+
$ planes : <qf_table>
29+
.. - Data: qf_table [3,322 x 9] (S3: qf_table/tbl_df/tbl/data.frame)
30+
.. .. $ tailnum : chr [1:3322] "N10156" "N102UW" "N103US" "N104UW" ...
31+
.. .. $ year : int [1:3322] 2004 1998 1999 1999 2002 1999 1999 1999 1999 1999 ...
32+
.. .. $ type : chr [1:3322] "Fixed wing multi engine" "Fixed wing multi engine" "Fixed wing multi engine" "Fixed wing multi engine" ...
33+
.. .. $ manufacturer: chr [1:3322] "EMBRAER" "AIRBUS INDUSTRIE" "AIRBUS INDUSTRIE" "AIRBUS INDUSTRIE" ...
34+
.. .. $ model : chr [1:3322] "EMB-145XR" "A320-214" "A320-214" "A320-214" ...
35+
.. .. $ engines : int [1:3322] 2 2 2 2 2 2 2 2 2 2 ...
36+
.. .. $ seats : int [1:3322] 55 182 182 182 55 182 182 182 182 182 ...
37+
.. .. $ speed : int [1:3322] NA NA NA NA NA NA NA NA NA NA ...
38+
.. .. $ engine : chr [1:3322] "Turbo-fan" "Turbo-fan" "Turbo-fan" "Turbo-fan" ...
39+
.. - Constraints:
40+
.. .. - <cstr_primary_key> [tailnum]
41+
$ airlines: <qf_table>
42+
.. - Data: qf_table [16 x 2] (S3: qf_table/tbl_df/tbl/data.frame)
43+
.. .. $ carrier: chr [1:16] "9E" "AA" "AS" "B6" ...
44+
.. .. $ name : chr [1:16] "Endeavor Air Inc." "American Airlines Inc." "Alaska Airlines Inc." "JetBlue Airways" ...
45+
.. - Constraints:
46+
.. .. - <cstr_primary_key> [carrier]
47+
.. .. - <cstr_not_missing> [name]
48+

tests/testthat/_snaps/qf_table.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# print() output is correct
2+
3+
<qf_table>
4+
- Data: qf_table [3,322 x 9] (S3: qf_table/tbl_df/tbl/data.frame)
5+
.. $ tailnum : chr [1:3322] "N10156" "N102UW" "N103US" "N104UW" ...
6+
.. $ year : int [1:3322] 2004 1998 1999 1999 2002 1999 1999 1999 1999 1999 ...
7+
.. $ type : chr [1:3322] "Fixed wing multi engine" "Fixed wing multi engine" "Fixed wing multi engine" "Fixed wing multi engine" ...
8+
.. $ manufacturer: chr [1:3322] "EMBRAER" "AIRBUS INDUSTRIE" "AIRBUS INDUSTRIE" "AIRBUS INDUSTRIE" ...
9+
.. $ model : chr [1:3322] "EMB-145XR" "A320-214" "A320-214" "A320-214" ...
10+
.. $ engines : int [1:3322] 2 2 2 2 2 2 2 2 2 2 ...
11+
.. $ seats : int [1:3322] 55 182 182 182 55 182 182 182 182 182 ...
12+
.. $ speed : int [1:3322] NA NA NA NA NA NA NA NA NA NA ...
13+
.. $ engine : chr [1:3322] "Turbo-fan" "Turbo-fan" "Turbo-fan" "Turbo-fan" ...
14+
- Constraints:
15+
.. - <cstr_primary_key> [tailnum]
16+
- Context: <qf_dataset>: flights, planes, airlines
17+

tests/testthat/helper.R

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
example_dataset <- function(constrained = TRUE) {
2+
dset <- qf_dataset(
3+
flights = nycflights13::flights,
4+
planes = nycflights13::planes,
5+
airlines = nycflights13::airlines
6+
)
7+
if (constrained) {
8+
constraints(dset$airlines) <- list(
9+
cstr_primary_key(carrier),
10+
cstr_not_missing(name)
11+
)
12+
constraints(dset$planes) <- list(
13+
cstr_primary_key(tailnum)
14+
)
15+
constraints(dset$flights) <- list(
16+
cstr_primary_key(c(year, month, day, carrier, flight)),
17+
cstr_foreign_key(carrier, airlines)
18+
)
19+
}
20+
dset
21+
}

tests/testthat/test-qf_constraint.R

Lines changed: 57 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
test_that("setting constraints works", {
2-
dset <- qf_dataset(
3-
flights = nycflights13::flights,
4-
planes = nycflights13::planes
5-
)
2+
dset <- example_dataset(constrained = FALSE)
63

74
constraints(dset$planes) <- list(
85
cstr_primary_key(tailnum)
@@ -34,14 +31,48 @@ test_that("setting constraints works", {
3431
)
3532
)
3633
)
34+
35+
# Foreign key referencing own table:
36+
enneagram <- as_qf_table(data.frame(
37+
number = c(1, 2, 3, 4, 5, 6, 7, 8, 9),
38+
disintegration = c(4, 8, 9, 2, 7, 3, 1, 5, 6),
39+
integration = c(7, 4, 6, 1, 8, 9, 5, 2, 3)
40+
))
41+
constraints(enneagram) <- list(
42+
cstr_primary_key(number),
43+
cstr_foreign_key(disintegration, .self$number),
44+
cstr_foreign_key(integration, .self$number)
45+
)
46+
expect_identical(
47+
constraints(enneagram),
48+
list(
49+
structure(
50+
list(cols = "number"),
51+
class = c("cstr_primary_key", "cstr_unique_key", "qf_constraint")
52+
),
53+
structure(
54+
list(cols = "disintegration", ref_table = ".self", ref_cols = "number"),
55+
class = c("cstr_foreign_key", "qf_constraint")
56+
),
57+
structure(
58+
list(cols = "integration", ref_table = ".self", ref_cols = "number"),
59+
class = c("cstr_foreign_key", "qf_constraint")
60+
)
61+
)
62+
)
63+
expect_error(regex = "no information on other tables in the dataset", {
64+
constraints(enneagram) <- list(
65+
cstr_primary_key(number),
66+
cstr_foreign_key(disintegration, .self$number),
67+
cstr_foreign_key(integration, .self$number),
68+
cstr_foreign_key(integration, some_other_table)
69+
)
70+
})
71+
3772
})
3873

3974
test_that("alternative reference specification formats are equivalent", {
40-
41-
dset <- qf_dataset(
42-
flights = nycflights13::flights,
43-
planes = nycflights13::planes
44-
)
75+
dset <- example_dataset(constrained = FALSE)
4576
constraints(dset$planes) <- list(cstr_primary_key(tailnum))
4677

4778
dset1 <- dset
@@ -58,27 +89,13 @@ test_that("alternative reference specification formats are equivalent", {
5889
)
5990
expect_identical(constraints(dset1$flights), constraints(dset2$flights))
6091
expect_identical(constraints(dset2$flights), constraints(dset3$flights))
61-
6292
})
6393

6494
test_that("constraint checking works", {
65-
66-
dset <- withr::with_package("nycflights13",
67-
qf_dataset(airlines, flights)
68-
)
69-
70-
constraints(dset$airlines) <- list(
71-
cstr_primary_key(carrier),
72-
cstr_not_missing(name)
73-
)
74-
constraints(dset$flights) <- list(
75-
cstr_primary_key(c(year, month, day, carrier, flight)),
76-
cstr_foreign_key(carrier, airlines)
77-
)
95+
dset <- example_dataset()
7896

7997
# Case 1: Primary key on 'flights' is invalid because of duplicate keys;
8098
# otherwise OK
81-
8299
result <- check_constraints(dset)
83100
expect_true(result$airlines[[1]]$satisfied)
84101
expect_true(result$airlines[[2]]$satisfied)
@@ -88,22 +105,33 @@ test_that("constraint checking works", {
88105
# Case 2: Primary key and not-missing constraint on 'airlines' is invalid
89106
# because of missing values for Delta Airlines; Foreign key on 'flights' is
90107
# invalid because it references this primary key.
91-
92108
dset2 <- dset
93109
dset2$airlines[dset$airlines$carrier == "DL", ] <- NA
94-
95110
result2 <- check_constraints(dset2)
96111
expect_false(result2$airlines[[1]]$satisfied)
97112
expect_false(result2$airlines[[2]]$satisfied)
98113
expect_false(result2$flights[[1]]$satisfied)
99114
expect_false(result2$flights[[2]]$satisfied)
100115

116+
# Foreign key referencing own table:
117+
enneagram <- as_qf_table(data.frame(
118+
number = c(1, 2, 3, 4, 5, 6, 7, 8, 9),
119+
disintegration = c(4, 8, 9, 2, 7, 3, 1, 5, 6),
120+
integration = c(7, 4, 6, 1, 8, 9, 5, 2, 3)
121+
))
122+
constraints(enneagram) <- list(
123+
cstr_primary_key(number),
124+
cstr_foreign_key(disintegration, .self$number),
125+
cstr_foreign_key(integration, .self$number)
126+
)
127+
result9 <- check_constraints(enneagram)
128+
expect_true(result9[[2]]$satisfied)
129+
expect_true(result9[[3]]$satisfied)
130+
101131
})
102132

103133
test_that("constraint validators work", {
104-
dset <- withr::with_package("nycflights13",
105-
qf_dataset(airlines, flights)
106-
)
134+
dset <- example_dataset(constrained = FALSE)
107135

108136
constraints(dset$airlines) <- list(
109137
cstr_not_missing(name)

0 commit comments

Comments
 (0)