From 574e249c8db2a3d99d2af91e1e91de0bed1ab35f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= <csardi.gabor@gmail.com>
Date: Wed, 1 Jun 2022 11:52:37 +0200
Subject: [PATCH 01/10] PoC of markdown summary on GHA

---
 R/gha-summary.R                         | 80 +++++++++++++++++++++++++
 R/parallel.R                            |  2 +
 R/test-files.R                          |  2 +
 tests/testthat/test-parallel-crash.R    |  5 +-
 tests/testthat/test-parallel-outside.R  |  5 +-
 tests/testthat/test-parallel-setup.R    |  5 +-
 tests/testthat/test-parallel-startup.R  |  5 +-
 tests/testthat/test-parallel-teardown.R |  5 +-
 tests/testthat/test-parallel.R          |  5 +-
 tests/testthat/test-reporter-list.R     |  5 ++
 tests/testthat/test-snapshot-reporter.R |  2 +
 tests/testthat/test-teardown.R          |  1 +
 tests/testthat/test-test-files.R        | 25 ++++++--
 13 files changed, 137 insertions(+), 10 deletions(-)
 create mode 100644 R/gha-summary.R

diff --git a/R/gha-summary.R b/R/gha-summary.R
new file mode 100644
index 000000000..f948817e8
--- /dev/null
+++ b/R/gha-summary.R
@@ -0,0 +1,80 @@
+
+create_gha_summary <- function(results) {
+  nope <- c("false", "no", "off", "n", "0", "nope", "nay")
+  if (tolower(Sys.getenv("TESTTHAT_GHA_SUMMARY")) %in% nope) {
+    return()
+  }
+  if ((out <- Sys.getenv("GITHUB_STEP_SUMMARY")) == "") {
+    return()
+  }
+
+  p <- function(...) cat(..., file = out, append = TRUE)
+
+  # header
+  p("# Test results\n\n")
+  p("| FAIL | WARN | SKIP | PASS | Context | Test | Time |\n")
+  p("|-----:|-----:|-----:|-----:|:--------|:-----|:-----|\n")
+
+  # one line per test
+  per_test <- lapply(results, gha_file_summary, p = p)
+
+  # totals
+  totals <- lapply(do.call(rbind, per_test), sum)
+  p(paste0(
+    "|", totals$fail,
+    "|", totals$warn,
+    "|", totals$skip,
+    "|", totals$ok,
+    "|", "Total",
+    "|", "",
+    "|", sprintf("%.3f s", totals$time),
+    "|\n"
+  ))
+
+  invisible(results)
+}
+
+gha_file_summary <- function(result, p) {
+
+  n_fail <- n_skip <- n_warn <- n_ok <- 0L
+  for (exp in result$results) {
+    if (expectation_broken(exp)) {
+      n_fail <- n_fail + 1L
+    } else if (expectation_skip(exp)) {
+      n_skip <- n_skip + 1L
+    } else if (expectation_warning(exp)) {
+      n_warn <- n_warn + 1L
+    } else {
+      n_ok <- n_ok + 1L
+    }
+  }
+
+  ctx <- context_name(result$file)
+  time <- sprintf("%.3f s", result$real)
+
+  escape <- function(x) {
+    x <- gsub("|", "\\|", x, fixed = TRUE)
+    x <- gsub("\n", " ", x, fixed = TRUE)
+    x
+  }
+
+  p(paste0(
+    "|", n_fail,
+    "|", n_warn,
+    "|", n_skip,
+    "|", n_ok,
+    "|", escape(ctx),
+    "|", escape(result$test),
+    "|", time,
+    "|\n"
+  ))
+
+  data.frame(
+    stringsAsFactors = FALSE,
+    fail = n_fail,
+    skip = n_skip,
+    warn = n_warn,
+    ok = n_ok,
+    time = result$real
+  )
+}
diff --git a/R/parallel.R b/R/parallel.R
index c8621078a..3a3a3e14e 100644
--- a/R/parallel.R
+++ b/R/parallel.R
@@ -79,6 +79,8 @@ test_files_parallel <- function(
     }
   })
 
+  create_gha_summary(reporters$list$get_results())
+
   test_files_check(reporters$list$get_results(),
     stop_on_failure = stop_on_failure,
     stop_on_warning = stop_on_warning
diff --git a/R/test-files.R b/R/test-files.R
index 2f846751e..0a8b645c4 100644
--- a/R/test-files.R
+++ b/R/test-files.R
@@ -204,6 +204,8 @@ test_files_serial <- function(test_dir,
     lapply(test_paths, test_one_file, env = env, wrap = wrap)
   )
 
+  create_gha_summary(reporters$list$get_results())
+
   test_files_check(reporters$list$get_results(),
     stop_on_failure = stop_on_failure,
     stop_on_warning = stop_on_warning
diff --git a/tests/testthat/test-parallel-crash.R b/tests/testthat/test-parallel-crash.R
index 0201f1cfd..e377ce175 100644
--- a/tests/testthat/test-parallel-crash.R
+++ b/tests/testthat/test-parallel-crash.R
@@ -4,7 +4,10 @@ test_that("crash", {
 
   skip_on_cran()
   skip_on_covr()
-  withr::local_envvar(TESTTHAT_PARALLEL = "TRUE")
+  withr::local_envvar(c(
+    TESTTHAT_PARALLEL = "TRUE",
+    TESTTHAT_GHA_SUMMARY = "FALSE"
+  ))
 
   do <- function() {
     err <- NULL
diff --git a/tests/testthat/test-parallel-outside.R b/tests/testthat/test-parallel-outside.R
index 0c4ed4a3e..8dea0e9b5 100644
--- a/tests/testthat/test-parallel-outside.R
+++ b/tests/testthat/test-parallel-outside.R
@@ -1,6 +1,9 @@
 
 test_that("error outside of test_that()", {
-  withr::local_envvar(TESTTHAT_PARALLEL = "TRUE")
+  withr::local_envvar(c(
+    TESTTHAT_PARALLEL = "TRUE",
+    TESTTHAT_GHA_SUMMARY = "FALSE"
+  ))
   err <- tryCatch(
     suppressMessages(testthat::test_local(
       test_path("test-parallel", "outside"),
diff --git a/tests/testthat/test-parallel-setup.R b/tests/testthat/test-parallel-setup.R
index eff1de715..1738a63ae 100644
--- a/tests/testthat/test-parallel-setup.R
+++ b/tests/testthat/test-parallel-setup.R
@@ -1,7 +1,10 @@
 
 test_that("error in parallel setup code", {
   skip_on_covr()
-  withr::local_envvar(TESTTHAT_PARALLEL = "TRUE")
+  withr::local_envvar(c(
+    TESTTHAT_PARALLEL = "TRUE",
+    TESTTHAT_GHA_SUMMARY = "FALSE"
+  ))
   err <- tryCatch(
     suppressMessages(testthat::test_local(
       test_path("test-parallel", "setup"),
diff --git a/tests/testthat/test-parallel-startup.R b/tests/testthat/test-parallel-startup.R
index 6daf444af..f6fa3c1a6 100644
--- a/tests/testthat/test-parallel-startup.R
+++ b/tests/testthat/test-parallel-startup.R
@@ -1,7 +1,10 @@
 
 test_that("startup error", {
   skip_on_covr()
-  withr::local_envvar(TESTTHAT_PARALLEL = "TRUE")
+  withr::local_envvar(c(
+    TESTTHAT_PARALLEL = "TRUE",
+    TESTTHAT_GHA_SUMMARY = "FALSE"
+  ))
   err <- tryCatch(
     suppressMessages(testthat::test_local(
       test_path("test-parallel", "startup"),
diff --git a/tests/testthat/test-parallel-teardown.R b/tests/testthat/test-parallel-teardown.R
index 9f0bcb089..8f2641ae9 100644
--- a/tests/testthat/test-parallel-teardown.R
+++ b/tests/testthat/test-parallel-teardown.R
@@ -1,7 +1,10 @@
 
 test_that("teardown error", {
   skip("teardown errors are ignored")
-  withr::local_envvar(TESTTHAT_PARALLEL = "TRUE")
+  withr::local_envvar(c(
+    TESTTHAT_PARALLEL = "TRUE",
+    TESTTHAT_GHA_SUMMARY = "FALSE"
+  ))
   err <- tryCatch(
     suppressMessages(testthat::test_local(
       test_path("test-parallel", "teardown"),
diff --git a/tests/testthat/test-parallel.R b/tests/testthat/test-parallel.R
index 6026dae17..20c662d26 100644
--- a/tests/testthat/test-parallel.R
+++ b/tests/testthat/test-parallel.R
@@ -23,7 +23,10 @@ test_that("detect number of cpus to use", {
 })
 
 test_that("ok", {
-  withr::local_envvar(c(TESTTHAT_PARALLEL = "TRUE"))
+  withr::local_envvar(c(
+    TESTTHAT_PARALLEL = "TRUE",
+    TESTTHAT_GHA_SUMMARY = "FALSE"
+  ))
   suppressMessages(ret <- test_local(
     test_path("test-parallel", "ok"),
     reporter = "silent",
diff --git a/tests/testthat/test-reporter-list.R b/tests/testthat/test-reporter-list.R
index 14dc35da5..9d89d7f25 100644
--- a/tests/testthat/test-reporter-list.R
+++ b/tests/testthat/test-reporter-list.R
@@ -1,6 +1,7 @@
 
 # regression test: test_file() used to crash with a NULL reporter
 test_that("ListReporter with test_file and NULL reporter", {
+  withr::local_envvar(TESTTHAT_GHA_SUMMARY = "FALSE")
   test_file_path <- 'test-list-reporter/test-exercise-list-reporter.R'
   expect_error(test_file(test_path(test_file_path), reporter = NULL), NA)
 })
@@ -9,6 +10,7 @@ test_that("ListReporter with test_file and NULL reporter", {
 # of a test (test_that() call).
 # N.B: the exception here happens between two tests: "before" and "after"
 test_that("ListReporter - exception outside of test_that()", {
+  withr::local_envvar(TESTTHAT_GHA_SUMMARY = "FALSE")
   test_file_path <- 'test-list-reporter/test-exception-outside-tests.R'
   res <- test_file(test_path(test_file_path), reporter = NULL)
 
@@ -31,6 +33,7 @@ test_that("ListReporter - exception outside of test_that()", {
 
 
 test_that("captures error if only thing in file", {
+  withr::local_envvar(TESTTHAT_GHA_SUMMARY = "FALSE")
   test_file_path <- 'test-list-reporter/test-only-error.R'
   res <- test_file(test_path(test_file_path), reporter = NULL)
 
@@ -40,6 +43,7 @@ test_that("captures error if only thing in file", {
 
 # ListReporter on a "standard" test file: 2 contexts, passing, failing and crashing tests
 test_that("exercise ListReporter", {
+  withr::local_envvar(TESTTHAT_GHA_SUMMARY = "FALSE")
   test_file_path <- 'test-list-reporter/test-exercise-list-reporter.R'
   res <- test_file(test_path(test_file_path), reporter = NULL)
   expect_s3_class(res, "testthat_results")
@@ -60,6 +64,7 @@ test_that("exercise ListReporter", {
 
 # bare expectations are ignored
 test_that("ListReporter and bare expectations", {
+  withr::local_envvar(TESTTHAT_GHA_SUMMARY = "FALSE")
   test_file_path <- 'test-list-reporter/test-bare-expectations.R'
   res <- test_file(test_path(test_file_path), reporter = NULL)
 
diff --git a/tests/testthat/test-snapshot-reporter.R b/tests/testthat/test-snapshot-reporter.R
index 85f698e58..878dc41bc 100644
--- a/tests/testthat/test-snapshot-reporter.R
+++ b/tests/testthat/test-snapshot-reporter.R
@@ -144,6 +144,7 @@ test_that("errors in test doesn't change snapshot", {
 })
 
 test_that("skips and unexpected errors reset snapshots", {
+  withr::local_envvar(TESTTHAT_GHA_SUMMARY = "FALSE")
   regenerate <- FALSE
 
   if (regenerate) {
@@ -166,6 +167,7 @@ test_that("skips and unexpected errors reset snapshots", {
 })
 
 test_that("`expect_error()` can fail inside `expect_snapshot()`", {
+  withr::local_envvar(TESTTHAT_GHA_SUMMARY = "FALSE")
   out <- test_file(
     test_path("test-snapshot", "test-expect-condition.R"),
     reporter = NULL
diff --git a/tests/testthat/test-teardown.R b/tests/testthat/test-teardown.R
index 4adc396b9..a41d377e5 100644
--- a/tests/testthat/test-teardown.R
+++ b/tests/testthat/test-teardown.R
@@ -26,6 +26,7 @@ test_that("teardowns runs in order", {
 })
 
 test_that("teardown run after tests complete", {
+  withr::local_envvar(TESTTHAT_GHA_SUMMARY = "FALSE")
   test_file(test_path("test-teardown/test-teardown.R"), "silent")
   expect_false(file.exists(test_path("test-teardown/teardown.txt")))
 })
diff --git a/tests/testthat/test-test-files.R b/tests/testthat/test-test-files.R
index d696266df..0b8b1b868 100644
--- a/tests/testthat/test-test-files.R
+++ b/tests/testthat/test-test-files.R
@@ -1,14 +1,20 @@
 # test_dir() --------------------------------------------------------------
 
 test_that("stops on failure", {
-  withr::local_envvar(TESTTHAT_PARALLEL = "FALSE")
+  withr::local_envvar(c(
+    TESTTHAT_PARALLEL = "TRUE",
+    TESTTHAT_GHA_SUMMARY = "FALSE"
+  ))
   expect_error(
     test_dir(test_path("test_dir"), reporter = "silent")
   )
 })
 
 test_that("runs all tests and records output", {
-  withr::local_envvar(TESTTHAT_PARALLEL = "FALSE")
+  withr::local_envvar(c(
+    TESTTHAT_PARALLEL = "TRUE",
+    TESTTHAT_GHA_SUMMARY = "FALSE"
+  ))
   res <- test_dir(test_path("test_dir"), reporter = "silent", stop_on_failure = FALSE)
   df <- as.data.frame(res)
   df$user <- df$system <- df$real <- df$result <- NULL
@@ -27,7 +33,10 @@ test_that("complains if no files", {
 })
 
 test_that("can control if failures generate errors", {
-  withr::local_envvar(TESTTHAT_PARALLEL = "FALSE")
+  withr::local_envvar(c(
+    TESTTHAT_PARALLEL = "TRUE",
+    TESTTHAT_GHA_SUMMARY = "FALSE"
+  ))
   test_error <- function(...) {
     test_dir(test_path("test-error"), reporter = "silent", ...)
   }
@@ -37,7 +46,11 @@ test_that("can control if failures generate errors", {
 })
 
 test_that("can control if warnings errors", {
-  withr::local_envvar(TESTTHAT_PARALLEL = "FALSE")
+  withr::local_envvar(c(
+    TESTTHAT_PARALLEL = "TRUE",
+    TESTTHAT_GHA_SUMMARY = "FALSE"
+  ))
+
   test_warning <- function(...) {
     test_dir(test_path("test-warning"), reporter = "silent", ...)
   }
@@ -49,6 +62,10 @@ test_that("can control if warnings errors", {
 # test_file ---------------------------------------------------------------
 
 test_that("can test single file", {
+  withr::local_envvar(c(
+    TESTTHAT_PARALLEL = "TRUE",
+    TESTTHAT_GHA_SUMMARY = "FALSE"
+  ))
   out <- test_file(test_path("test_dir/test-basic.R"), reporter = "silent")
   expect_length(out, 5)
 })

From fa7509803ffa2b0606fc272d74b7ad2d27e645f6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= <csardi.gabor@gmail.com>
Date: Wed, 1 Jun 2022 12:34:33 +0200
Subject: [PATCH 02/10] No GHA summary in test_file()

To avoid the tables from running test_file() in examples.
Not the best solution.
---
 R/test-files.R | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/R/test-files.R b/R/test-files.R
index 0a8b645c4..e7fc5e152 100644
--- a/R/test-files.R
+++ b/R/test-files.R
@@ -137,6 +137,8 @@ test_file <- function(path, reporter = default_compact_reporter(), package = NUL
     stop("`path` does not exist", call. = FALSE)
   }
 
+  withr::local_envvar(TESTTHAT_GHA_SUMMARY = "false")
+
   test_files(
     test_dir = dirname(path),
     test_package = package,

From 045e26a1505cf934c8e1a852bf91970ec2453933 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= <csardi.gabor@gmail.com>
Date: Wed, 1 Jun 2022 12:39:14 +0200
Subject: [PATCH 03/10] Put whole GHA summary into a <details>

---
 R/gha-summary.R | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/R/gha-summary.R b/R/gha-summary.R
index f948817e8..31d15f65a 100644
--- a/R/gha-summary.R
+++ b/R/gha-summary.R
@@ -11,6 +11,7 @@ create_gha_summary <- function(results) {
   p <- function(...) cat(..., file = out, append = TRUE)
 
   # header
+  p("<details>\n")
   p("# Test results\n\n")
   p("| FAIL | WARN | SKIP | PASS | Context | Test | Time |\n")
   p("|-----:|-----:|-----:|-----:|:--------|:-----|:-----|\n")
@@ -31,6 +32,8 @@ create_gha_summary <- function(results) {
     "|\n"
   ))
 
+  p("</details>\n")
+
   invisible(results)
 }
 

From 0112c9ee8141e1b4145b674d004468fa6f1b1dae Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= <csardi.gabor@gmail.com>
Date: Wed, 1 Jun 2022 12:47:37 +0200
Subject: [PATCH 04/10] GHA summary: need empty line after <details>

---
 R/gha-summary.R | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/R/gha-summary.R b/R/gha-summary.R
index 31d15f65a..213442cdb 100644
--- a/R/gha-summary.R
+++ b/R/gha-summary.R
@@ -11,7 +11,7 @@ create_gha_summary <- function(results) {
   p <- function(...) cat(..., file = out, append = TRUE)
 
   # header
-  p("<details>\n")
+  p("<details>\n\n")
   p("# Test results\n\n")
   p("| FAIL | WARN | SKIP | PASS | Context | Test | Time |\n")
   p("|-----:|-----:|-----:|-----:|:--------|:-----|:-----|\n")
@@ -32,7 +32,7 @@ create_gha_summary <- function(results) {
     "|\n"
   ))
 
-  p("</details>\n")
+  p("\n</details>\n")
 
   invisible(results)
 }

From 7481df421302d07e2d7d7cb0cc9060ad9d17c324 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= <csardi.gabor@gmail.com>
Date: Wed, 1 Jun 2022 13:08:59 +0200
Subject: [PATCH 05/10] Fix tests

Meant PARALLEL = FALSE
---
 tests/testthat/test-test-files.R | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/tests/testthat/test-test-files.R b/tests/testthat/test-test-files.R
index 0b8b1b868..53a0066b0 100644
--- a/tests/testthat/test-test-files.R
+++ b/tests/testthat/test-test-files.R
@@ -2,7 +2,7 @@
 
 test_that("stops on failure", {
   withr::local_envvar(c(
-    TESTTHAT_PARALLEL = "TRUE",
+    TESTTHAT_PARALLEL = "FALSE",
     TESTTHAT_GHA_SUMMARY = "FALSE"
   ))
   expect_error(
@@ -12,7 +12,7 @@ test_that("stops on failure", {
 
 test_that("runs all tests and records output", {
   withr::local_envvar(c(
-    TESTTHAT_PARALLEL = "TRUE",
+    TESTTHAT_PARALLEL = "FALSE",
     TESTTHAT_GHA_SUMMARY = "FALSE"
   ))
   res <- test_dir(test_path("test_dir"), reporter = "silent", stop_on_failure = FALSE)
@@ -34,7 +34,7 @@ test_that("complains if no files", {
 
 test_that("can control if failures generate errors", {
   withr::local_envvar(c(
-    TESTTHAT_PARALLEL = "TRUE",
+    TESTTHAT_PARALLEL = "FALSE",
     TESTTHAT_GHA_SUMMARY = "FALSE"
   ))
   test_error <- function(...) {
@@ -47,7 +47,7 @@ test_that("can control if failures generate errors", {
 
 test_that("can control if warnings errors", {
   withr::local_envvar(c(
-    TESTTHAT_PARALLEL = "TRUE",
+    TESTTHAT_PARALLEL = "FALSE",
     TESTTHAT_GHA_SUMMARY = "FALSE"
   ))
 
@@ -63,7 +63,7 @@ test_that("can control if warnings errors", {
 
 test_that("can test single file", {
   withr::local_envvar(c(
-    TESTTHAT_PARALLEL = "TRUE",
+    TESTTHAT_PARALLEL = "FALSE",
     TESTTHAT_GHA_SUMMARY = "FALSE"
   ))
   out <- test_file(test_path("test_dir/test-basic.R"), reporter = "silent")

From 859be30dbabf2d945121741d658ed905e1779ed1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= <csardi.gabor@gmail.com>
Date: Wed, 1 Jun 2022 14:18:36 +0200
Subject: [PATCH 06/10] GHA summary: only issues

---
 R/gha-summary.R | 104 ++++++++++++++++++++++++------------------------
 1 file changed, 53 insertions(+), 51 deletions(-)

diff --git a/R/gha-summary.R b/R/gha-summary.R
index 213442cdb..4a136785e 100644
--- a/R/gha-summary.R
+++ b/R/gha-summary.R
@@ -9,75 +9,77 @@ create_gha_summary <- function(results) {
   }
 
   p <- function(...) cat(..., file = out, append = TRUE)
+  fmt_time <- function(x) sprintf("%.3fs", x)
 
-  # header
-  p("<details>\n\n")
-  p("# Test results\n\n")
-  p("| FAIL | WARN | SKIP | PASS | Context | Test | Time |\n")
-  p("|-----:|-----:|-----:|-----:|:--------|:-----|:-----|\n")
+  results <- lapply(results, gha_summarize_test)
+  totals <- list(
+    n_fail = sum(vapply(results, "[[", integer(1), "n_fail")),
+    n_warn = sum(vapply(results, "[[", integer(1), "n_warn")),
+    n_skip = sum(vapply(results, "[[", integer(1), "n_skip")),
+    n_ok   = sum(vapply(results, "[[", integer(1), "n_ok")),
+    real   = sum(vapply(results, "[[", double(1), "real"))
+  )
 
-  # one line per test
-  per_test <- lapply(results, gha_file_summary, p = p)
+  # summary
+  p("### Test summary\n\n")
+  p("| \u274c FAIL | \u26a0 WARN | \u23ed\ufe0f SKIP | \u2705 PASS | \u23f1 Time |\n")
+  p("|------------:|------------:|------------------:|------------:|:------------|\n")
 
-  # totals
-  totals <- lapply(do.call(rbind, per_test), sum)
   p(paste0(
-    "|", totals$fail,
-    "|", totals$warn,
-    "|", totals$skip,
-    "|", totals$ok,
-    "|", "Total",
-    "|", "",
-    "|", sprintf("%.3f s", totals$time),
+    "|", if (totals$n_fail > 0) paste0("\u274c **", totals$n_fail, "**"),
+    "|", if (totals$n_warn > 0) paste0("\u26a0 **", totals$n_warn, "**"),
+    "|", if (totals$n_skip > 0) paste0("\u23ed\ufe0f **", totals$n_skip, "**"),
+    "|", paste0("\u2705 **", totals$n_ok, "**"),
+    "|", fmt_time(totals$real),
     "|\n"
   ))
 
+  # tests with issues
+  p("\n<details>\n\n")
+
+  p("### Test details\n\n")
+  p("| \u274c FAIL | \u26a0 WARN | \u23ed\ufe0f SKIP | \u2705 PASS | context | test | \u23f1 Time |\n")
+  p("|------------:|------------:|------------------:|------------:|:--------|:-----|:------------|\n")
+
+  escape <- function(x) {
+    x <- gsub("|", "\\|", x, fixed = TRUE)
+    x <- gsub("\n", " ", x, fixed = TRUE)
+    x
+  }
+
+  issues <- Filter(function(x) length(x$results) != x$n_ok, results)
+  for (issue in issues) {
+    p(paste0(
+      "|", if (issue$n_fail > 0) paste0("\u274c **", issue$n_fail, "**"),
+      "|", if (issue$n_warn > 0) paste0("\u26a0 **", issue$n_warn, "**"),
+      "|", if (issue$n_skip > 0) paste0("\u23ed\ufe0f **", issue$n_skip, "**"),
+      "|", if (issue$n_ok   > 0) paste0("\u2705 **", issue$n_ok, "**"),
+      "|", escape(context_name(issue$file)),
+      "|", escape(issue$test),
+      "|", fmt_time(issue$real),
+      "|\n"
+    ))
+  }
+
   p("\n</details>\n")
 
   invisible(results)
 }
 
-gha_file_summary <- function(result, p) {
+gha_summarize_test <- function(test) {
 
-  n_fail <- n_skip <- n_warn <- n_ok <- 0L
-  for (exp in result$results) {
+  test$n_fail <- test$n_skip <- test$n_warn <- test$n_ok <- 0L
+  for (exp in test$results) {
     if (expectation_broken(exp)) {
-      n_fail <- n_fail + 1L
+      test$n_fail <- test$n_fail + 1L
     } else if (expectation_skip(exp)) {
-      n_skip <- n_skip + 1L
+      test$n_skip <- test$n_skip + 1L
     } else if (expectation_warning(exp)) {
-      n_warn <- n_warn + 1L
+      test$n_warn <- test$n_warn + 1L
     } else {
-      n_ok <- n_ok + 1L
+      test$n_ok <- test$n_ok + 1L
     }
   }
 
-  ctx <- context_name(result$file)
-  time <- sprintf("%.3f s", result$real)
-
-  escape <- function(x) {
-    x <- gsub("|", "\\|", x, fixed = TRUE)
-    x <- gsub("\n", " ", x, fixed = TRUE)
-    x
-  }
-
-  p(paste0(
-    "|", n_fail,
-    "|", n_warn,
-    "|", n_skip,
-    "|", n_ok,
-    "|", escape(ctx),
-    "|", escape(result$test),
-    "|", time,
-    "|\n"
-  ))
-
-  data.frame(
-    stringsAsFactors = FALSE,
-    fail = n_fail,
-    skip = n_skip,
-    warn = n_warn,
-    ok = n_ok,
-    time = result$real
-  )
+  test
 }

From 5c13a99fd2bb7f6cda0622fd5669293f10de38ab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= <csardi.gabor@gmail.com>
Date: Wed, 1 Jun 2022 14:39:31 +0200
Subject: [PATCH 07/10] GHA summary: need space between test suites

E.g. on multi-platform windows.
---
 R/gha-summary.R | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/R/gha-summary.R b/R/gha-summary.R
index 4a136785e..be4e29c47 100644
--- a/R/gha-summary.R
+++ b/R/gha-summary.R
@@ -61,7 +61,7 @@ create_gha_summary <- function(results) {
     ))
   }
 
-  p("\n</details>\n")
+  p("\n</details>\n\n\n")
 
   invisible(results)
 }

From 86479d8df19935eeef86afdc5c6591335f9efe30 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= <csardi.gabor@gmail.com>
Date: Wed, 1 Jun 2022 14:42:47 +0200
Subject: [PATCH 08/10] GHA summary: try to keep Unicode in output

---
 R/gha-summary.R | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/R/gha-summary.R b/R/gha-summary.R
index be4e29c47..ffa7e7355 100644
--- a/R/gha-summary.R
+++ b/R/gha-summary.R
@@ -8,6 +8,9 @@ create_gha_summary <- function(results) {
     return()
   }
 
+  out <- file(out, open = "wba", encoding = "UTF-8")
+  on.exit(close(out), add = TRUE)
+
   p <- function(...) cat(..., file = out, append = TRUE)
   fmt_time <- function(x) sprintf("%.3fs", x)
 

From 191147a5a37a1c29f6c106b38d3fdb9ea1179b2d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= <csardi.gabor@gmail.com>
Date: Wed, 1 Jun 2022 15:11:57 +0200
Subject: [PATCH 09/10] GHA summary: more UTF-8 tries on Windows

---
 R/gha-summary.R | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/R/gha-summary.R b/R/gha-summary.R
index ffa7e7355..10c98096e 100644
--- a/R/gha-summary.R
+++ b/R/gha-summary.R
@@ -8,10 +8,14 @@ create_gha_summary <- function(results) {
     return()
   }
 
-  out <- file(out, open = "wba", encoding = "UTF-8")
+  out <- file(out, open = "a+b", encoding = "unknown")
   on.exit(close(out), add = TRUE)
 
-  p <- function(...) cat(..., file = out, append = TRUE)
+  p <- function(...) {
+    s <- paste0(...)
+    Encoding(s) <- "unknown"
+    cat(s, file = out, append = TRUE)
+  }
   fmt_time <- function(x) sprintf("%.3fs", x)
 
   results <- lapply(results, gha_summarize_test)

From 861e1ff90bb397240d7cb0f10eca653d9eaf3cff Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= <csardi.gabor@gmail.com>
Date: Wed, 8 May 2024 21:01:25 +0200
Subject: [PATCH 10/10] Fix merge mistake

---
 tests/testthat/test-parallel.R | 1 -
 1 file changed, 1 deletion(-)

diff --git a/tests/testthat/test-parallel.R b/tests/testthat/test-parallel.R
index 4ff7d0f0f..305076bc9 100644
--- a/tests/testthat/test-parallel.R
+++ b/tests/testthat/test-parallel.R
@@ -27,7 +27,6 @@ test_that("ok", {
     TESTTHAT_PARALLEL = "TRUE",
     TESTTHAT_GHA_SUMMARY = "FALSE"
   ))
-  suppressMessages(ret <- test_local(
   capture.output(suppressMessages(ret <- test_local(
     test_path("test-parallel", "ok"),
     reporter = "summary",