Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add metadata tests for all linters #2393

Merged
merged 39 commits into from
Dec 8, 2023
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
caa26d6
Attempt to get GHA going
MichaelChirico Dec 5, 2023
916bde4
Two fixes to ensure they're caught
MichaelChirico Dec 5, 2023
5c6b684
fix
MichaelChirico Dec 5, 2023
432c4e8
need lax=FALSE
MichaelChirico Dec 5, 2023
e056664
Need fixed=TRUE
MichaelChirico Dec 5, 2023
52a9eef
Correct line numbers
MichaelChirico Dec 5, 2023
59e760e
oops
MichaelChirico Dec 5, 2023
03b48a6
Add some future-proofing
MichaelChirico Dec 5, 2023
1ba0809
Attempt to include path_linter_factory
MichaelChirico Dec 5, 2023
8ee76fe
missing ,
MichaelChirico Dec 5, 2023
d54b5f7
keep vector names
MichaelChirico Dec 5, 2023
88378c8
spacing
MichaelChirico Dec 5, 2023
a73f80a
Apply directly to Lint()
MichaelChirico Dec 6, 2023
586e4ff
Merge branch 'main' into ensure-metadata
MichaelChirico Dec 6, 2023
cfea37f
Merge remote-tracking branch 'origin/ensure-metadata' into ensure-met…
MichaelChirico Dec 6, 2023
da94526
linters [a-c]
MichaelChirico Dec 6, 2023
98861ed
even local-friendlier
MichaelChirico Dec 6, 2023
7e8edfc
ush excet= handling to Xath
MichaelChirico Dec 6, 2023
e1d4fd4
small otimization: ski 1-kwarg calls
MichaelChirico Dec 6, 2023
4a15737
simlify
MichaelChirico Dec 6, 2023
258dc07
fix for #2402
MichaelChirico Dec 6, 2023
cda1c01
linters <exect_s4_class_linter
MichaelChirico Dec 6, 2023
efa7262
?
MichaelChirico Dec 6, 2023
7ce25e6
add templates for all cases, fill out through i
MichaelChirico Dec 7, 2023
e3b18e1
Another batch
MichaelChirico Dec 7, 2023
05cd63f
first pass done
MichaelChirico Dec 7, 2023
6d8542d
NEWS
MichaelChirico Dec 7, 2023
445f28f
Merge remote-tracking branch 'origin/main' into ensure-metadata
MichaelChirico Dec 7, 2023
3f5b4b1
Merge remote-tracking branch 'origin/ensure-metadata' into ensure-met…
MichaelChirico Dec 7, 2023
3ada1c6
fix broken tests, finish metadata tests
MichaelChirico Dec 7, 2023
3f1b81c
fix for #2402
MichaelChirico Dec 6, 2023
4846ace
NEWS
MichaelChirico Dec 7, 2023
38bb576
delint
MichaelChirico Dec 7, 2023
a1e05dd
more tests
MichaelChirico Dec 8, 2023
da68c13
Merge branch 'dup-comment' into ensure-metadata
MichaelChirico Dec 8, 2023
30a78fb
more tests
MichaelChirico Dec 8, 2023
3133854
Merge branch 'main' into ensure-metadata
AshesITR Dec 8, 2023
a3b37f5
fix NEWS.md merge
AshesITR Dec 8, 2023
dd57ec5
Update test-unnecessary_nesting_linter.R
AshesITR Dec 8, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions .dev/lint_metadata_test.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# This script is designed to find linters that lack metadata tests.
# To do so, it forces Lint() to give the wrong information,
# runs the test suite, and finds linters that nevertheless pass all their tests.
library(testthat)

lint_file <- "R/lint.R"

original <- readLines(lint_file)
expected_line <- "line_number = as.integer(line_number)"
if (sum(grepl(expected_line, original, fixed = TRUE)) != 1L) {
stop(sprintf(
"Please update this workflow -- need exactly one hit for line '%s' in file '%s'.",
expected_line, lint_file
))
}
writeLines(
sub(expected_line, "line_number = as.integer(2^31 - 1)", original, fixed = TRUE),
lint_file
)
# Not useful in CI but good when running locally.
withr::defer({
writeLines(original, lint_file)
pkgload::load_all()
})

pkgload::load_all()
IndrajeetPatil marked this conversation as resolved.
Show resolved Hide resolved

report <- test_dir(
"tests/testthat",
filter = "linter$",
stop_on_failure = FALSE,
reporter = SilentReporter$new()
)
names(report) <- gsub("^test-|\\.R$", "", vapply(report, `[[`, "file", FUN.VALUE = character(1L)))

# Hack the nested structure of the testthat report to identify which files have
# any failed test
failed <- report |>
vapply(
\(x) any(vapply(x$results, inherits, "expectation_failure", FUN.VALUE = logical(1L))),
logical(1L)
) |>
which() |>
names() |>
unique()

passed <- setdiff(
available_linters(tags = NULL)$linter,
failed
)

if (length(passed) > 0L) {
stop("Please add tests of lint metadata for the following linters: ", toString(passed))
IndrajeetPatil marked this conversation as resolved.
Show resolved Hide resolved
}
30 changes: 30 additions & 0 deletions .github/workflows/ensure-metadata-tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Ensure lint metadata is tested
on:
push:
branches: [main]
pull_request:
branches: [main]

name: ensure-metadata-tests

jobs:
ensure-metadata-tests:
runs-on: ubuntu-latest
env:
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}

steps:
- uses: actions/checkout@v3

- uses: r-lib/actions/setup-r@v2
with:
r-version: "release"
use-public-rspm: true

- uses: r-lib/actions/setup-r-dependencies@v2

- name: Ensure lint metadata is tested
MichaelChirico marked this conversation as resolved.
Show resolved Hide resolved
run: |
options(crayon.enabled = TRUE)
callr::rscript(".dev/lint_metadata_test.R")
shell: Rscript {0}
19 changes: 19 additions & 0 deletions tests/testthat/test-absolute_path_linter.R
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,22 @@ test_that("raw strings are handled correctly", {
absolute_path_linter(lax = FALSE)
)
})

test_that("lints vectorize", {
lint_msg <- rex::rex("Do not use absolute paths.")

expect_lint(
trim_some("{
'/'
'/blah/file.txt'
'abcdefg'
'~'
}"),
list(
list(lint_msg, line_number = 2L),
list(lint_msg, line_number = 3L),
list(lint_msg, line_number = 5L)
),
absolute_path_linter(lax = FALSE)
)
})
111 changes: 38 additions & 73 deletions tests/testthat/test-any_duplicated_linter.R
Original file line number Diff line number Diff line change
Expand Up @@ -10,112 +10,77 @@ test_that("any_duplicated_linter skips allowed usages", {
})

test_that("any_duplicated_linter blocks simple disallowed usages", {
expect_lint(
"any(duplicated(x))",
rex::rex("anyDuplicated(x, ...) > 0 is better"),
any_duplicated_linter()
)

expect_lint(
"any(duplicated(foo(x)))",
rex::rex("anyDuplicated(x, ...) > 0 is better"),
any_duplicated_linter()
)
linter <- any_duplicated_linter()
lint_msg <- rex::rex("anyDuplicated(x, ...) > 0 is better")

expect_lint("any(duplicated(x))", lint_msg, linter)
expect_lint("any(duplicated(foo(x)))", lint_msg, linter)
# na.rm doesn't really matter for this since duplicated can't return NA
expect_lint(
"any(duplicated(x), na.rm = TRUE)",
rex::rex("anyDuplicated(x, ...) > 0 is better"),
any_duplicated_linter()
)

expect_lint("any(duplicated(x), na.rm = TRUE)", lint_msg, linter)
# also catch nested usage
expect_lint(
"foo(any(duplicated(x)))",
rex::rex("anyDuplicated(x, ...) > 0 is better"),
any_duplicated_linter()
)
expect_lint("foo(any(duplicated(x)))", lint_msg, linter)
})

test_that("any_duplicated_linter catches length(unique()) equivalencies too", {
linter <- any_duplicated_linter()
lint_msg_x <- rex::rex("anyDuplicated(x) == 0L is better than length(unique(x)) == length(x)")
lint_msg_df <- rex::rex("anyDuplicated(DF$col) == 0L is better than length(unique(DF$col)) == nrow(DF)")

# non-matches
## different variable
expect_lint("length(unique(x)) == length(y)", NULL, any_duplicated_linter())
expect_lint("length(unique(x)) == length(y)", NULL, linter)
## different table
expect_lint("length(unique(DF$x)) == nrow(DT)", NULL, any_duplicated_linter())
expect_lint("length(unique(l1$DF$x)) == nrow(l2$DF)", NULL, any_duplicated_linter())
expect_lint("length(unique(DF$x)) == nrow(DT)", NULL, linter)
expect_lint("length(unique(l1$DF$x)) == nrow(l2$DF)", NULL, linter)

# lintable usage
expect_lint(
"length(unique(x)) == length(x)",
rex::rex("anyDuplicated(x) == 0L is better than length(unique(x)) == length(x)"),
any_duplicated_linter()
)
expect_lint("length(unique(x)) == length(x)", lint_msg_x, linter)
# argument order doesn't matter
expect_lint(
"length(x) == length(unique(x))",
rex::rex("anyDuplicated(x) == 0L is better than length(unique(x)) == length(x)"),
any_duplicated_linter()
)
expect_lint("length(x) == length(unique(x))", lint_msg_x, linter)
# nrow-style equivalency
expect_lint(
"nrow(DF) == length(unique(DF$col))",
rex::rex("anyDuplicated(DF$col) == 0L is better than length(unique(DF$col)) == nrow(DF)"),
any_duplicated_linter()
)
expect_lint(
"nrow(DF) == length(unique(DF[['col']]))",
rex::rex("anyDuplicated(DF$col) == 0L is better than length(unique(DF$col)) == nrow(DF)"),
any_duplicated_linter()
)
expect_lint("nrow(DF) == length(unique(DF$col))", lint_msg_df, linter)
expect_lint("nrow(DF) == length(unique(DF[['col']]))", lint_msg_df, linter)
# match with nesting too
expect_lint(
"nrow(l$DF) == length(unique(l$DF[['col']]))",
rex::rex("anyDuplicated(DF$col) == 0L is better than length(unique(DF$col)) == nrow(DF)"),
any_duplicated_linter()
)
expect_lint("nrow(l$DF) == length(unique(l$DF[['col']]))", lint_msg_df, linter)

# !=, <, and > usages are all alternative ways of writing a test for dupes
# technically, the direction of > / < matter, but writing
# length(unique(x)) > length(x) doesn't seem like it would ever happen.
expect_lint(
"length(unique(x)) != length(x)",
rex::rex("anyDuplicated(x) == 0L is better than length(unique(x)) == length(x)"),
any_duplicated_linter()
)
expect_lint(
"length(unique(x)) < length(x)",
rex::rex("anyDuplicated(x) == 0L is better than length(unique(x)) == length(x)"),
any_duplicated_linter()
)
expect_lint(
"length(x) > length(unique(x))",
rex::rex("anyDuplicated(x) == 0L is better than length(unique(x)) == length(x)"),
any_duplicated_linter()
)
expect_lint("length(unique(x)) != length(x)", lint_msg_x, linter)
expect_lint("length(unique(x)) < length(x)", lint_msg_x, linter)
expect_lint("length(x) > length(unique(x))", lint_msg_x, linter)

# TODO(michaelchirico): try and match data.table- and dplyr-specific versions of
# this, e.g. DT[, length(unique(col)) == .N] or
# > DT %>% filter(length(unique(col)) == n())
})

test_that("any_duplicated_linter catches expression with two types of lint", {
linter <- any_duplicated_linter()
lint_msg <- rex::rex("anyDuplicated(DF$col) == 0L is better than length(unique(DF$col)) == nrow(DF)")

expect_lint(
"table(any(duplicated(x)), length(unique(DF$col)) == nrow(DF))",
trim_some("{
any(duplicated(x))
length(unique(DF$col)) == nrow(DF)
}"),
list(
rex::rex("anyDuplicated(x, ...) > 0 is better"),
rex::rex("anyDuplicated(DF$col) == 0L is better than length(unique(DF$col)) == nrow(DF)")
list(rex::rex("anyDuplicated(x, ...) > 0 is better"), line_number = 2L),
list(lint_msg, line_number = 3L)
),
any_duplicated_linter()
linter
)

# ditto for different messages within the length(unique()) tests
expect_lint(
"table(length(unique(x)) == length(x), length(unique(DF$col)) == nrow(DF))",
trim_some("{
length(unique(x)) == length(x)
length(unique(DF$col)) == nrow(DF)
}"),
list(
rex::rex("anyDuplicated(x) == 0L is better than length(unique(x)) == length(x)"),
rex::rex("anyDuplicated(DF$col) == 0L is better than length(unique(DF$col)) == nrow(DF)")
list(rex::rex("anyDuplicated(x) == 0L is better than length(unique(x)) == length(x)"), line_number = 2L),
list(lint_msg, line_number = 3L)
),
any_duplicated_linter()
linter
)
})
27 changes: 21 additions & 6 deletions tests/testthat/test-any_is_na_linter.R
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,29 @@ test_that("any_is_na_linter skips allowed usages", {
})

test_that("any_is_na_linter blocks simple disallowed usages", {
linter <- any_is_na_linter()
lint_message <- rex::rex("anyNA(x) is better than any(is.na(x)).")
expect_lint("any(is.na(x))", lint_message, any_is_na_linter())

expect_lint("any(is.na(foo(x)))", lint_message, any_is_na_linter())

expect_lint("any(is.na(x))", lint_message, linter)
expect_lint("any(is.na(foo(x)))", lint_message, linter)
# na.rm doesn't really matter for this since is.na can't return NA
expect_lint("any(is.na(x), na.rm = TRUE)", lint_message, any_is_na_linter())

expect_lint("any(is.na(x), na.rm = TRUE)", lint_message, linter)
# also catch nested usage
expect_lint("foo(any(is.na(x)))", lint_message, any_is_na_linter())
expect_lint("foo(any(is.na(x)))", lint_message, linter)
})

test_that("lints vectorize", {
lint_message <- rex::rex("anyNA(x) is better than any(is.na(x)).")

expect_lint(
trim_some("{
any(is.na(foo(x)))
any(is.na(y), na.rm = TRUE)
}"),
list(
list(lint_message, line_number = 2L),
list(lint_message, line_number = 3L)
),
any_is_na_linter()
)
})
15 changes: 10 additions & 5 deletions tests/testthat/test-assignment_linter.R
Original file line number Diff line number Diff line change
Expand Up @@ -177,12 +177,17 @@ test_that("%<>% throws a lint", {

test_that("multiple lints throw correct messages", {
expect_lint(
"{ x <<- 1; y ->> 2; z -> 3; x %<>% as.character() }",
trim_some("{
x <<- 1
y ->> 2
z -> 3
x %<>% as.character()
}"),
list(
list(message = "Replace <<- by assigning to a specific environment"),
list(message = "Replace ->> by assigning to a specific environment"),
list(message = "Use <-, not ->"),
list(message = "Avoid the assignment pipe %<>%")
list(message = "Replace <<- by assigning to a specific environment", line_number = 2L),
list(message = "Replace ->> by assigning to a specific environment", line_number = 3L),
list(message = "Use <-, not ->", line_number = 4L),
list(message = "Avoid the assignment pipe %<>%", line_number = 5L)
),
assignment_linter(allow_cascading_assign = FALSE)
)
Expand Down
10 changes: 7 additions & 3 deletions tests/testthat/test-backport_linter.R
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,14 @@ test_that("backport_linter detects backwards-incompatibility", {
)

expect_lint(
"trimws(...names())",
trim_some("
trimws(
...names()
)
"),
list(
rex::rex("trimws (R 3.2.0) is not available for dependency R >= 3.0.0."),
rex::rex("...names (R 4.1.0) is not available for dependency R >= 3.0.0.")
list(rex::rex("trimws (R 3.2.0) is not available for dependency R >= 3.0.0."), line_number = 1L),
list(rex::rex("...names (R 4.1.0) is not available for dependency R >= 3.0.0."), line_number = 2L)
),
backport_linter("3.0.0")
)
Expand Down
16 changes: 16 additions & 0 deletions tests/testthat/test-boolean_arithmetic_linter.R
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,19 @@ test_that("boolean_arithmetic_linter requires use of any() or !any()", {
expect_lint("sum(x == y) != 0", lint_msg, linter)
expect_lint("sum(grepl(pattern, x)) > 0L", lint_msg, linter)
})

test_that("lints vectorize", {
lint_msg <- rex::rex("Use any() to express logical aggregations.")

expect_lint(
trim_some("{
length(which(x == y)) > 0L
sum(x == y) != 0
}"),
list(
list(lint_msg, line_number = 2L),
list(lint_msg, line_number = 3L)
),
boolean_arithmetic_linter()
)
})
16 changes: 16 additions & 0 deletions tests/testthat/test-class_equals_linter.R
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,19 @@ test_that("class_equals_linter skips usage for subsetting", {
linter
)
})

test_that("lints vectorize", {
lint_msg <- rex::rex("Use inherits(x, 'class-name'), is.<class> or is(x, 'class')")

expect_lint(
trim_some("{
'character' %in% class(x)
class(x) == 'character'
}"),
list(
list(lint_msg, line_number = 2L),
list(lint_msg, line_number = 3L)
),
class_equals_linter()
)
})
Loading
Loading