From 9d5d5aa585f1397d857762aa0c8054c31e0b423f Mon Sep 17 00:00:00 2001 From: leake Date: Fri, 14 Nov 2025 18:57:57 -0700 Subject: [PATCH 1/8] Adding simple tests that run the full CLI. --- tests/cli_test.py | 85 +++++++++++++++++++ .../sample header with spaces.h | 50 +++++++++++ 2 files changed, 135 insertions(+) create mode 100644 tests/cli_test.py create mode 100644 tests/sample_header_docs/sample header with spaces.h diff --git a/tests/cli_test.py b/tests/cli_test.py new file mode 100644 index 0000000..a575cfb --- /dev/null +++ b/tests/cli_test.py @@ -0,0 +1,85 @@ +import os +from pathlib import Path +from tempfile import NamedTemporaryFile +from os import system + +DIR = Path(__file__).absolute().parents[0] + +expected = """\ +/* + This file contains docstrings for use in the Python bindings. + Do not edit! They were automatically extracted by pybind11_mkdoc. + */ + +#define __EXPAND(x) x +#define __COUNT(_1, _2, _3, _4, _5, _6, _7, COUNT, ...) COUNT +#define __VA_SIZE(...) __EXPAND(__COUNT(__VA_ARGS__, 7, 6, 5, 4, 3, 2, 1, 0)) +#define __CAT1(a, b) a ## b +#define __CAT2(a, b) __CAT1(a, b) +#define __DOC1(n1) __doc_##n1 +#define __DOC2(n1, n2) __doc_##n1##_##n2 +#define __DOC3(n1, n2, n3) __doc_##n1##_##n2##_##n3 +#define __DOC4(n1, n2, n3, n4) __doc_##n1##_##n2##_##n3##_##n4 +#define __DOC5(n1, n2, n3, n4, n5) __doc_##n1##_##n2##_##n3##_##n4##_##n5 +#define __DOC6(n1, n2, n3, n4, n5, n6) __doc_##n1##_##n2##_##n3##_##n4##_##n5##_##n6 +#define __DOC7(n1, n2, n3, n4, n5, n6, n7) __doc_##n1##_##n2##_##n3##_##n4##_##n5##_##n6##_##n7 +#define DOC(...) __EXPAND(__EXPAND(__CAT2(__DOC, __VA_SIZE(__VA_ARGS__)))(__VA_ARGS__)) + +#if defined(__GNUG__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" +#endif + + +static const char *__doc_RootLevelSymbol = +R"doc(Root-level symbol. Magna fermentum iaculis eu non diam phasellus +vestibulum.)doc"; + +static const char *__doc_drake_MidLevelSymbol = +R"doc(1. Begin first ordered list element. Rutrum quisque non tellus orci ac +auctor. End first ordered list element. 2. Begin second ordered list +element. Ipsum faucibus vitae aliquet nec. Ligula ullamcorper +malesuada proin libero. End second ordered list element. 3. Begin +third ordered list element. Dictum sit amet justo donec enim. Pharetra +convallis posuere morbi leo urna molestie. End third ordered list +element. + +Senectus et netus et malesuada fames ac. Tincidunt lobortis feugiat +vivamus at augue eget arcu dictum varius.)doc"; + +#if defined(__GNUG__) +#pragma GCC diagnostic pop +#endif + +""" + + +def test_simple_header_cli(capsys): + # Run pybind11-mkdoc and put the output in a temp file + tf = NamedTemporaryFile(suffix=".h") + header = DIR / "sample_header_docs" / "sample_header.h" + exit_code = system(f"python -m pybind11_mkdoc -o {tf.name} {header}") + + # Ensure pybind11-mkdoc ran successfully + assert exit_code == 0 + + # Ensure the header file matches + with open(tf.name, "r") as f: + res = f.read() + + assert res == expected + +def test_simple_header_with_spaces_cli(capsys): + # Run pybind11-mkdoc and put the output in a temp file + tf = NamedTemporaryFile(suffix=".h") + header = DIR / "sample_header_docs" / "sample header with spaces.h" + exit_code = system(f"python -m pybind11_mkdoc -o {tf.name} \"{header}\"") + + # Ensure pybind11-mkdoc ran successfully + assert exit_code == 0 + + # Ensure the header file matches + with open(tf.name, "r") as f: + res = f.read() + + assert res == expected diff --git a/tests/sample_header_docs/sample header with spaces.h b/tests/sample_header_docs/sample header with spaces.h new file mode 100644 index 0000000..acce531 --- /dev/null +++ b/tests/sample_header_docs/sample header with spaces.h @@ -0,0 +1,50 @@ +#pragma once + +/// @dir +/// Directory documentation is ignored. Sit amet massa vitae tortor. Pulvinar +/// pellentesque habitant morbi tristique senectus et. Lacus sed turpis +/// tincidunt id. + +/// @file +/// File documentation is ignored. Sit amet nisl purus in mollis nunc sed id +/// semper. Risus nec feugiat in fermentum posuere urna nec tincidunt praesent. +/// Suscipit tellus mauris a diam. + +/// @defgroup first_group Elementum pulvinar etiam non quam lacus. +/// Ultrices in iaculis nunc sed augue lacus viverra. Dolor sit amet +/// consectetur adipiscing elit duis tristique. + +#include +#include + + +/// @def PREPROCESSOR_DEFINITION +/// Preprocessor definitions are ignored. In nibh mauris cursus mattis +/// molestie a. Non arcu risus quis varius quam quisque id. +#define PREPROCESSOR_DEFINITION "Nisl purus in mollis nunc sed id." + +/// Root-level symbol. Magna fermentum iaculis eu non diam phasellus +/// vestibulum. +struct RootLevelSymbol {}; + +/// @namespace drake +/// Namespaces are ignored. Enim blandit volutpat maecenas volutpat blandit. Eu +/// feugiat pretium nibh ipsum consequat. +namespace drake { + +/** + * 1. Begin first ordered list element. Rutrum quisque non tellus orci ac + * auctor. End first ordered list element. + * 2. Begin second ordered list element. Ipsum faucibus vitae aliquet nec. + * Ligula ullamcorper malesuada proin libero. End second ordered list + * element. + * 3. Begin third ordered list element. Dictum sit amet justo donec enim. + * Pharetra convallis posuere morbi leo urna molestie. End third ordered + * list element. + * + * Senectus et netus et malesuada fames ac. Tincidunt lobortis feugiat vivamus + * at augue eget arcu dictum varius. + */ +struct MidLevelSymbol {}; + +} // namespace drake From f13b90de1f1010ed5fc82c7ce95b8428bf03763d Mon Sep 17 00:00:00 2001 From: leake Date: Fri, 14 Nov 2025 19:04:05 -0700 Subject: [PATCH 2/8] Updating from master. --- tests/cli_test.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/tests/cli_test.py b/tests/cli_test.py index a575cfb..31b6be0 100644 --- a/tests/cli_test.py +++ b/tests/cli_test.py @@ -11,19 +11,18 @@ Do not edit! They were automatically extracted by pybind11_mkdoc. */ -#define __EXPAND(x) x -#define __COUNT(_1, _2, _3, _4, _5, _6, _7, COUNT, ...) COUNT -#define __VA_SIZE(...) __EXPAND(__COUNT(__VA_ARGS__, 7, 6, 5, 4, 3, 2, 1, 0)) -#define __CAT1(a, b) a ## b -#define __CAT2(a, b) __CAT1(a, b) -#define __DOC1(n1) __doc_##n1 -#define __DOC2(n1, n2) __doc_##n1##_##n2 -#define __DOC3(n1, n2, n3) __doc_##n1##_##n2##_##n3 -#define __DOC4(n1, n2, n3, n4) __doc_##n1##_##n2##_##n3##_##n4 -#define __DOC5(n1, n2, n3, n4, n5) __doc_##n1##_##n2##_##n3##_##n4##_##n5 -#define __DOC6(n1, n2, n3, n4, n5, n6) __doc_##n1##_##n2##_##n3##_##n4##_##n5##_##n6 -#define __DOC7(n1, n2, n3, n4, n5, n6, n7) __doc_##n1##_##n2##_##n3##_##n4##_##n5##_##n6##_##n7 -#define DOC(...) __EXPAND(__EXPAND(__CAT2(__DOC, __VA_SIZE(__VA_ARGS__)))(__VA_ARGS__)) +#define MKD_EXPAND(x) x +#define MKD_COUNT(_1, _2, _3, _4, _5, _6, _7, COUNT, ...) COUNT +#define MKD_VA_SIZE(...) MKD_EXPAND(MKD_COUNT(__VA_ARGS__, 7, 6, 5, 4, 3, 2, 1, 0)) +#define MKD_CAT1(a, b) a ## b +#define MKD_CAT2(a, b) MKD_CAT1(a, b) +#define MKD_DOC1(n1) mkd_doc_##n1 +#define MKD_DOC2(n1, n2) mkd_doc_##n1##_##n2 +#define MKD_DOC3(n1, n2, n3) mkd_doc_##n1##_##n2##_##n3 +#define MKD_DOC4(n1, n2, n3, n4) mkd_doc_##n1##_##n2##_##n3##_##n4 +#define MKD_DOC5(n1, n2, n3, n4, n5) mkd_doc_##n1##_##n2##_##n3##_##n4##_##n5 +#define MKD_DOC7(n1, n2, n3, n4, n5, n6, n7) mkd_doc_##n1##_##n2##_##n3##_##n4##_##n5##_##n6##_##n7 +#define DOC(...) MKD_EXPAND(MKD_EXPAND(MKD_CAT2(MKD_DOC, MKD_VA_SIZE(__VA_ARGS__)))(__VA_ARGS__)) #if defined(__GNUG__) #pragma GCC diagnostic push @@ -31,11 +30,11 @@ #endif -static const char *__doc_RootLevelSymbol = +static const char *mkd_doc_RootLevelSymbol = R"doc(Root-level symbol. Magna fermentum iaculis eu non diam phasellus vestibulum.)doc"; -static const char *__doc_drake_MidLevelSymbol = +static const char *mkd_doc_drake_MidLevelSymbol = R"doc(1. Begin first ordered list element. Rutrum quisque non tellus orci ac auctor. End first ordered list element. 2. Begin second ordered list element. Ipsum faucibus vitae aliquet nec. Ligula ullamcorper From c7490dcffe9848090911f3396000edc8915c206b Mon Sep 17 00:00:00 2001 From: leake Date: Fri, 14 Nov 2025 19:06:29 -0700 Subject: [PATCH 3/8] Just using a symlink to avoid duplication. --- .../sample header with spaces.h | 51 +------------------ 1 file changed, 1 insertion(+), 50 deletions(-) mode change 100644 => 120000 tests/sample_header_docs/sample header with spaces.h diff --git a/tests/sample_header_docs/sample header with spaces.h b/tests/sample_header_docs/sample header with spaces.h deleted file mode 100644 index acce531..0000000 --- a/tests/sample_header_docs/sample header with spaces.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once - -/// @dir -/// Directory documentation is ignored. Sit amet massa vitae tortor. Pulvinar -/// pellentesque habitant morbi tristique senectus et. Lacus sed turpis -/// tincidunt id. - -/// @file -/// File documentation is ignored. Sit amet nisl purus in mollis nunc sed id -/// semper. Risus nec feugiat in fermentum posuere urna nec tincidunt praesent. -/// Suscipit tellus mauris a diam. - -/// @defgroup first_group Elementum pulvinar etiam non quam lacus. -/// Ultrices in iaculis nunc sed augue lacus viverra. Dolor sit amet -/// consectetur adipiscing elit duis tristique. - -#include -#include - - -/// @def PREPROCESSOR_DEFINITION -/// Preprocessor definitions are ignored. In nibh mauris cursus mattis -/// molestie a. Non arcu risus quis varius quam quisque id. -#define PREPROCESSOR_DEFINITION "Nisl purus in mollis nunc sed id." - -/// Root-level symbol. Magna fermentum iaculis eu non diam phasellus -/// vestibulum. -struct RootLevelSymbol {}; - -/// @namespace drake -/// Namespaces are ignored. Enim blandit volutpat maecenas volutpat blandit. Eu -/// feugiat pretium nibh ipsum consequat. -namespace drake { - -/** - * 1. Begin first ordered list element. Rutrum quisque non tellus orci ac - * auctor. End first ordered list element. - * 2. Begin second ordered list element. Ipsum faucibus vitae aliquet nec. - * Ligula ullamcorper malesuada proin libero. End second ordered list - * element. - * 3. Begin third ordered list element. Dictum sit amet justo donec enim. - * Pharetra convallis posuere morbi leo urna molestie. End third ordered - * list element. - * - * Senectus et netus et malesuada fames ac. Tincidunt lobortis feugiat vivamus - * at augue eget arcu dictum varius. - */ -struct MidLevelSymbol {}; - -} // namespace drake diff --git a/tests/sample_header_docs/sample header with spaces.h b/tests/sample_header_docs/sample header with spaces.h new file mode 120000 index 0000000..48dbf9b --- /dev/null +++ b/tests/sample_header_docs/sample header with spaces.h @@ -0,0 +1 @@ +sample_header.h \ No newline at end of file From a0f21fbd9edc1d18f38b5ed9e0b155f879e3f8d7 Mon Sep 17 00:00:00 2001 From: leake Date: Fri, 14 Nov 2025 19:08:50 -0700 Subject: [PATCH 4/8] Fixing ruff format errors. --- tests/cli_test.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/cli_test.py b/tests/cli_test.py index 31b6be0..660c51d 100644 --- a/tests/cli_test.py +++ b/tests/cli_test.py @@ -1,4 +1,3 @@ -import os from pathlib import Path from tempfile import NamedTemporaryFile from os import system @@ -53,7 +52,7 @@ """ -def test_simple_header_cli(capsys): +def test_simple_header_cli(): # Run pybind11-mkdoc and put the output in a temp file tf = NamedTemporaryFile(suffix=".h") header = DIR / "sample_header_docs" / "sample_header.h" @@ -68,7 +67,7 @@ def test_simple_header_cli(capsys): assert res == expected -def test_simple_header_with_spaces_cli(capsys): +def test_simple_header_with_spaces_cli(): # Run pybind11-mkdoc and put the output in a temp file tf = NamedTemporaryFile(suffix=".h") header = DIR / "sample_header_docs" / "sample header with spaces.h" From 784b217792b863911e6708e624f538eef460636f Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 14 Nov 2025 21:30:03 -0500 Subject: [PATCH 5/8] Apply suggestion from @henryiii --- tests/cli_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cli_test.py b/tests/cli_test.py index 660c51d..ac7080b 100644 --- a/tests/cli_test.py +++ b/tests/cli_test.py @@ -2,7 +2,7 @@ from tempfile import NamedTemporaryFile from os import system -DIR = Path(__file__).absolute().parents[0] +DIR = Path(__file__).resolve().parent expected = """\ /* From dcb0c0dcf20063dfdd3a041205ff8e586591ea4b Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 14 Nov 2025 21:35:12 -0500 Subject: [PATCH 6/8] Refactor cli tests to improve file handling Refactor tests to use tmp_path for temporary files and subprocess for command execution. --- tests/cli_test.py | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/tests/cli_test.py b/tests/cli_test.py index ac7080b..c2951e3 100644 --- a/tests/cli_test.py +++ b/tests/cli_test.py @@ -1,6 +1,6 @@ +import subprocess from pathlib import Path -from tempfile import NamedTemporaryFile -from os import system + DIR = Path(__file__).resolve().parent @@ -52,32 +52,25 @@ """ -def test_simple_header_cli(): +def test_simple_header_cli(tmp_path: Path) -> None: # Run pybind11-mkdoc and put the output in a temp file - tf = NamedTemporaryFile(suffix=".h") + tf = tmp_path / "tmp.h" header = DIR / "sample_header_docs" / "sample_header.h" - exit_code = system(f"python -m pybind11_mkdoc -o {tf.name} {header}") - - # Ensure pybind11-mkdoc ran successfully - assert exit_code == 0 + subprocess.run([sys.executable, "-m", "pybind11_mkdoc", "-o", tf, header], check=True) # Ensure the header file matches - with open(tf.name, "r") as f: - res = f.read() + res = tf.read_text(encoding="utf-8") assert res == expected -def test_simple_header_with_spaces_cli(): +def test_simple_header_with_spaces_cli(tmp_path: Path) -> None: # Run pybind11-mkdoc and put the output in a temp file - tf = NamedTemporaryFile(suffix=".h") + tf = tmp_path / "tmp.h" header = DIR / "sample_header_docs" / "sample header with spaces.h" - exit_code = system(f"python -m pybind11_mkdoc -o {tf.name} \"{header}\"") + subprocess.run([sys.executable, "-m", "pybind11_mkdoc", "-o", tf, header], check=True) - # Ensure pybind11-mkdoc ran successfully - assert exit_code == 0 # Ensure the header file matches - with open(tf.name, "r") as f: - res = f.read() + res = tf.read_text(encoding="utf-8") assert res == expected From cd5402a3aded15b9c97809b31a37268836c98ec9 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 14 Nov 2025 21:36:22 -0500 Subject: [PATCH 7/8] Add import for sys module in cli_test.py --- tests/cli_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/cli_test.py b/tests/cli_test.py index c2951e3..7fb0ec6 100644 --- a/tests/cli_test.py +++ b/tests/cli_test.py @@ -1,3 +1,4 @@ +import sys import subprocess from pathlib import Path From e392108637028aad9ad59d75e9805d40fa019fbf Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 14 Nov 2025 21:39:39 -0500 Subject: [PATCH 8/8] Refactor test_simple_header_cli with parameterization --- tests/cli_test.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/tests/cli_test.py b/tests/cli_test.py index 7fb0ec6..6829cd5 100644 --- a/tests/cli_test.py +++ b/tests/cli_test.py @@ -2,6 +2,7 @@ import subprocess from pathlib import Path +import pytest DIR = Path(__file__).resolve().parent @@ -53,24 +54,17 @@ """ -def test_simple_header_cli(tmp_path: Path) -> None: +@pytest.mark.parametrize( + "name", + ["sample_header.h", "sample header with spaces.h"], + ids=["no_spaces", "spaces"], +) +def test_simple_header_cli(tmp_path: Path, name: str) -> None: # Run pybind11-mkdoc and put the output in a temp file tf = tmp_path / "tmp.h" - header = DIR / "sample_header_docs" / "sample_header.h" + header = DIR / "sample_header_docs" / name subprocess.run([sys.executable, "-m", "pybind11_mkdoc", "-o", tf, header], check=True) - # Ensure the header file matches - res = tf.read_text(encoding="utf-8") - - assert res == expected - -def test_simple_header_with_spaces_cli(tmp_path: Path) -> None: - # Run pybind11-mkdoc and put the output in a temp file - tf = tmp_path / "tmp.h" - header = DIR / "sample_header_docs" / "sample header with spaces.h" - subprocess.run([sys.executable, "-m", "pybind11_mkdoc", "-o", tf, header], check=True) - - # Ensure the header file matches res = tf.read_text(encoding="utf-8")