Skip to content

Commit 8968e2d

Browse files
authored
Add doc tests. (#12216)
1 parent 41591ee commit 8968e2d

11 files changed

Lines changed: 364 additions & 18 deletions

File tree

.github/workflows/misc.yml

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,35 @@ jobs:
133133
--prefix ${BRANCH_NAME}/${{ github.event.pull_request.head.sha || github.sha }} --make-public \
134134
r-docs-${BRANCH_NAME}.tar.bz2
135135
136+
test-docs:
137+
name: Test Sphinx documentation snippets
138+
runs-on: ubuntu-latest
139+
defaults:
140+
run:
141+
shell: bash -l {0}
142+
steps:
143+
- uses: actions/checkout@v6.0.2
144+
with:
145+
submodules: "true"
146+
- name: Install system dependencies
147+
run: |
148+
sudo apt update
149+
sudo apt install -y libuv1-dev
150+
- uses: r-lib/actions/setup-r@v2
151+
with:
152+
r-version: release
153+
- uses: dmlc/xgboost-devops/actions/miniforge-setup@main
154+
with:
155+
environment-name: xgboost_docs
156+
environment-file: doc/xgboost_doc.yml
157+
- uses: dmlc/xgboost-devops/actions/sccache@main
158+
- name: Test Sphinx documentation snippets
159+
run: bash ops/pipeline/test-docs.sh
160+
- run: sccache --show-stats
161+
136162
trigger-rtd-build:
137163
name: Trigger Read The Docs build
138-
needs: [build-jvm-docs, build-r-docs]
164+
needs: [build-jvm-docs, build-r-docs, test-docs]
139165
runs-on:
140166
- runs-on=${{ github.run_id }}
141167
- runner=linux-amd64-cpu

R-package/DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,6 @@ Imports:
7171
data.table (>= 1.9.6),
7272
jsonlite (>= 1.0)
7373
Roxygen: list(markdown = TRUE)
74-
RoxygenNote: 7.3.3
7574
Encoding: UTF-8
7675
SystemRequirements: GNU make, C++17
76+
Config/roxygen2/version: 8.0.0

R-package/man/xgb.plot.deepness.Rd

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

R-package/man/xgb.plot.shap.Rd

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
## Install dependencies of R package for testing. The list might not be
22
## up-to-date, check DESCRIPTION for the latest list and update this one if
33
## inconsistent is found.
4-
pkgs <- c(
5-
## CI
4+
ci_pkgs <- c(
65
"pkgbuild",
76
"roxygen2",
87
"XML",
98
"cplm",
10-
"e1071",
11-
## suggests
9+
"e1071"
10+
)
11+
12+
suggests_pkgs <- c(
1213
"knitr",
1314
"rmarkdown",
1415
"ggplot2",
@@ -23,31 +24,62 @@ pkgs <- c(
2324
"igraph",
2425
"float",
2526
"titanic",
26-
"RhpcBLASctl",
27-
## imports
27+
"RhpcBLASctl"
28+
)
29+
30+
imports_pkgs <- c(
2831
"Matrix",
2932
"data.table",
3033
"jsonlite"
3134
)
3235

36+
dependency_scopes <- list(
37+
ci = ci_pkgs,
38+
suggests = suggests_pkgs,
39+
imports = imports_pkgs,
40+
doc_test = c(imports_pkgs, "DirichletReg", "testthat")
41+
)
42+
43+
scopes <- commandArgs(trailingOnly = TRUE)
44+
if (!length(scopes)) {
45+
scopes <- c("ci", "suggests", "imports")
46+
}
47+
scopes <- gsub("-", "_", scopes, fixed = TRUE)
48+
if ("all" %in% scopes) {
49+
scopes <- names(dependency_scopes)
50+
}
51+
52+
unknown_scopes <- setdiff(scopes, names(dependency_scopes))
53+
if (length(unknown_scopes)) {
54+
stop(
55+
"Unknown dependency scope(s): ",
56+
paste(unknown_scopes, collapse = ", "),
57+
". Valid scopes are: ",
58+
paste(c(names(dependency_scopes), "all"), collapse = ", ")
59+
)
60+
}
61+
62+
pkgs <- unique(unlist(dependency_scopes[scopes], use.names = FALSE))
63+
3364
ncpus <- parallel::detectCores()
3465
print(paste0("Using ", ncpus, " cores to install dependencies."))
66+
print(paste0("Installing dependency scopes: ", paste(scopes, collapse = ", ")))
3567

3668
if (.Platform$OS.type == "unix") {
3769
print("Installing source packages on unix.")
3870
install.packages(
3971
pkgs,
40-
repo = "https://cloud.r-project.org",
72+
repos = "https://cloud.r-project.org",
4173
dependencies = c("Depends", "Imports", "LinkingTo"),
42-
Ncpus = parallel::detectCores()
74+
Ncpus = ncpus
4375
)
4476
} else {
4577
print("Installing binary packages on Windows.")
4678
install.packages(
4779
pkgs,
48-
repo = "https://cloud.r-project.org",
80+
repos = "https://cloud.r-project.org",
4981
dependencies = c("Depends", "Imports", "LinkingTo"),
50-
Ncpus = parallel::detectCores(),
82+
Ncpus = ncpus,
5183
type = "binary"
5284
)
5385
}

doc/conf.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,15 +252,31 @@ def is_readthedocs_build():
252252
"sphinxcontrib.jquery",
253253
"sphinx.ext.autodoc",
254254
"sphinx.ext.napoleon",
255+
"sphinx.ext.doctest",
255256
"sphinx.ext.mathjax",
256257
"sphinx.ext.intersphinx",
257258
"sphinx_gallery.gen_gallery",
258259
"sphinx_issues",
259260
"sphinx_tabs.tabs",
260261
"breathe",
261262
"myst_parser",
263+
"xgboost_doc_doctest",
262264
]
263265

266+
sphinx_tabs_valid_builders = ["html", "doctest"]
267+
268+
# We need the real XGBoost library for running tests.
269+
doctest_global_setup = """
270+
import os
271+
import sys
272+
273+
os.environ.pop("XGBOOST_BUILD_DOC", None)
274+
for name in list(sys.modules):
275+
if name == "xgboost" or name.startswith("xgboost."):
276+
del sys.modules[name]
277+
"""
278+
doctest_test_doctest_blocks = ""
279+
264280
sphinx_gallery_conf = {
265281
# path to your example scripts
266282
"examples_dirs": [

doc/contrib/docs.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,22 @@ Examples
126126
* We are super excited to hear about your story. If you have blog posts,
127127
tutorials, or code solutions using XGBoost, please tell us, and we will add
128128
a link in the example pages.
129+
130+
*********
131+
Doc Tests
132+
*********
133+
134+
We use Sphinx doctest to test selected snippets in the documentation. At the moment, this
135+
only covers Python and R snippets written with ``.. code-tab:: python`` or ``.. code-tab::
136+
r``. Regular code blocks are rendered as examples but are not executed by the doctest job.
137+
138+
The doctest job runs snippets from each ``.rst`` file as an independent group. Snippets in
139+
the same document share state, while snippets from different documents do not. To skip a
140+
tabbed snippet, add the ``no-doctest`` class:
141+
142+
.. code-block:: rst
143+
144+
.. code-tab:: python
145+
:class: no-doctest
146+
147+
# This snippet is rendered but not tested.

doc/tutorials/advanced_custom_obj.rst

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -316,12 +316,12 @@ point, which means it will be a minimum rather than a maximum or saddle point).
316316
k = 3
317317
rng = np.random.default_rng(seed=123)
318318
x0 = rng.standard_normal(size=k)
319-
y_sample = rng.dirichlet(np.exp(x0), size=5_000_000)
319+
y_sample = rng.dirichlet(np.exp(x0), size=200_000)
320320
x_broadcast = np.broadcast_to(x0, (y_sample.shape[0], k))
321321
g_sample = dirichlet_grad(x_broadcast, y_sample)
322322
ref = (g_sample.T @ g_sample) / y_sample.shape[0]
323323
Ehess = dirichlet_expected_hess(x0.reshape((1,-1)))[0]
324-
np.testing.assert_almost_equal(Ehess, ref, decimal=2)
324+
np.testing.assert_allclose(Ehess, ref, rtol=5e-2, atol=5e-2)
325325
test_dirichlet_expected_hess()
326326

327327
.. code-tab:: r R
@@ -344,14 +344,14 @@ point, which means it will be a minimum rather than a maximum or saddle point).
344344
set.seed(123)
345345
x0 <- rnorm(k)
346346
alpha <- exp(x0)
347-
n.samples <- 5e6
347+
n.samples <- 2e5
348348
y.samples <- rdirichlet(n.samples, alpha)
349349

350350
x.broadcast <- rep(x0, n.samples) |> matrix(ncol=k, byrow=T)
351351
grad.samples <- dirichlet.grad(x.broadcast, y.samples)
352352
ref <- crossprod(grad.samples) / n.samples
353353
Ehess <- dirichlet.expected.hess(matrix(x0, nrow=1))
354-
expect_equal(Ehess[1,,], ref, tolerance=1e-2)
354+
expect_equal(Ehess[1,,], ref, tolerance=5e-2)
355355
})
356356
357357
But note that this is still not usable for XGBoost, since the expected
@@ -528,6 +528,7 @@ Fitting an XGBoost model and making predictions:
528528
evals=[(dtrain, "Train")],
529529
evals_result=results,
530530
custom_metric=dirichlet_eval_metric,
531+
verbose_eval=False,
531532
)
532533
yhat = softmax(booster.inplace_predict(X), axis=1)
533534

@@ -655,6 +656,7 @@ Now fitting a model again, this time with the intercept:
655656
evals=[(dtrain, "Train")],
656657
evals_result=results,
657658
custom_metric=dirichlet_eval_metric,
659+
verbose_eval=False,
658660
)
659661
yhat = softmax(
660662
booster.predict(

doc/xgboost_doc.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,15 @@ dependencies:
1212
- numpy
1313
- scipy
1414
- scikit-learn
15+
- dask
1516
- myst-parser
1617
- pyspark
1718
- pip:
1819
- breathe
1920
- sphinx_rtd_theme
21+
- sphinx-issues
22+
- sphinx-tabs
23+
- sphinxcontrib-jquery
2024
- pydot-ng
2125
- graphviz
2226
- ray[train]

0 commit comments

Comments
 (0)