Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[submodule "external/org-mode"]
path = testdata/external/org-mode
url = https://github.com/bzg/org-mode.git
shallow = true
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,14 @@ The tests show additional supported features:

## Development and documentation

Clone with `git clone --recurse-submodules https://github.com/karlicoss/orgparse.git` to include the test corpus.
For an existing checkout, run `git submodule update --init --recursive`.

Run the tests with `uv tool run --with tox-uv tox -e tests`.
This also checks the examples in this README.

The normal test suite parses the `.org` files in `testdata/external/org-mode/testing/examples` and checks tree structure and attribute access.

Edit `README.qmd`, then regenerate `README.md` with `uv tool run --with tox-uv tox -e quarto`.
Quarto computes links to source code and tests from their definitions, so line numbers are refreshed when rendering.
Commit both files together; CI checks that the generated README is current.
Expand Down
5 changes: 5 additions & 0 deletions README.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,14 @@ The tests show additional supported features:

## Development and documentation

Clone with `git clone --recurse-submodules https://github.com/karlicoss/orgparse.git` to include the test corpus.
For an existing checkout, run `git submodule update --init --recursive`.

Run the tests with `uv tool run --with tox-uv tox -e tests`.
This also checks the examples in this README.

The normal test suite parses the `.org` files in `testdata/external/org-mode/testing/examples` and checks tree structure and attribute access.

Edit `README.qmd`, then regenerate `README.md` with `uv tool run --with tox-uv tox -e quarto`.
Quarto computes links to source code and tests from their definitions, so line numbers are refreshed when rendering.
Commit both files together; CI checks that the generated README is current.
Expand Down
9 changes: 8 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ quarto = [
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"

[tool.hatch.build]
# Upstream test corpora are development inputs and retain their own licenses.
exclude = ["/testdata/external"]

# unfortunately have to duplicate project name here atm, see https://github.com/pypa/hatch/issues/1894
[tool.hatch.build.targets.wheel]
packages = ["src/orgparse"]
Expand Down Expand Up @@ -111,7 +115,7 @@ enable_error_code = [
# suppressing rules/changing error level goes here

[tool.ty.src]
# exclude = [...]
exclude = ["testdata/external"]


[tool.ruff]
Expand Down Expand Up @@ -231,13 +235,16 @@ lint.ignore = [
# "scratch/**",
# ]
extend-exclude = [
"testdata/external",
]

[tool.ruff.format]
quote-style = "preserve"


[tool.pytest.ini_options]
testpaths = ["src/orgparse"]

# discover files that don't follow test_ naming. Useful to keep tests along with the source code
python_files = ["*.py"]

Expand Down
70 changes: 70 additions & 0 deletions src/orgparse/tests/test_corpus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""Exercise the public parser API on Org mode's upstream example files."""

from pathlib import Path

import pytest

from .. import load
from ..extra import Table
from ..node import OrgNode

CORPUS = Path(__file__).resolve().parents[3] / 'testdata' / 'external' / 'org-mode' / 'testing' / 'examples'


def corpus_files() -> list[Path]:
paths = sorted(CORPUS.rglob('*.org'))
assert len(paths) > 0, 'Missing Org corpus: run git submodule update --init --recursive'
return paths


@pytest.mark.parametrize('path', corpus_files(), ids=lambda path: path.relative_to(CORPUS).as_posix())
def test_corpus(path: Path) -> None:
"""Check parser robustness and tree consistency across upstream Org examples.

Exercise heading/body formatting, property lookups, table row/block iteration,
and formatting of populated timestamps.
Check tag inclusion, parent/child links, a shared root, increasing heading line numbers,
and heading levels against the source text.
These are smoke and consistency checks; exact parsed values require separate expected-output tests.
"""
root = load(path)
lines = path.read_text(encoding='utf-8').splitlines()
assert root.is_root()
assert root.parent is None

previous_line = 0
for node in root:
# Access lazy formatting as well as the eagerly parsed attributes.
assert isinstance(node.heading, str)
assert isinstance(node.body, str)
assert node.shallow_tags <= node.tags
for key, value in node.properties.items():
assert node.get_property(key) == value
for part in node.body_rich:
if isinstance(part, Table):
list(part.rows)
list(part.blocks)

dates = node.get_timestamps(active=True, inactive=True)
if isinstance(node, OrgNode):
dates = [*dates, node.scheduled, node.deadline, node.closed, *node.clock, *node.repeated_tasks]
for date in dates:
if date.start is not None:
str(date)

assert node.root is root
for child in node.children:
assert child.parent is node
assert child.level > node.level
if node is root:
continue

assert previous_line < node.linenumber <= len(lines)
previous_line = node.linenumber
heading_line = lines[node.linenumber - 1]
assert heading_line[: node.level] == '*' * node.level
assert heading_line[node.level].isspace()
parent = node.parent
assert parent is not None
assert parent.level < node.level
assert any(child is node for child in parent.children)
1 change: 1 addition & 0 deletions testdata/external/org-mode
Submodule org-mode added at 9aae2e
Loading