pyodide/docs/sphinx_pyodide/tests/test_directives.py

355 lines
11 KiB
Python

import gzip
import inspect
import json
import sys
from pathlib import Path
from docutils.frontend import OptionParser
from docutils.utils import new_document
if not hasattr(inspect, "getargspec"):
inspect.getargspec = inspect.getfullargspec # type: ignore[assignment]
from sphinx_js.suffix_tree import SuffixTree
from sphinx_js.typedoc import Analyzer as TsAnalyzer
test_directory = Path(__file__).resolve().parent
sys.path.append(str(test_directory.parent))
src_dir = test_directory.parents[2] / "src"
# tsdoc_dump.json.gz is the source file for the test docs. It can be updated as follows:
#
# cp src/core/pyproxy.ts src/js/pyproxy.gen.ts
# typedoc src/js/*.ts --tsconfig src/js/tsconfig.json --json docs/sphinx_pyodide/tests/tsdoc_dump.json
# gzip docs/sphinx_pyodide/tests/tsdoc_dump.json
# rm src/js/pyproxy.gen.ts
with gzip.open(test_directory / "tsdoc_dump.json.gz") as fh:
jsdoc_json = json.load(fh)
settings_json = json.loads((test_directory / "app_settings.json").read_text())
from sphinx_pyodide.jsdoc import (
PyodideAnalyzer,
flatten_suffix_tree,
get_jsdoc_content_directive,
get_jsdoc_summary_directive,
)
inner_analyzer = TsAnalyzer(jsdoc_json, str(src_dir))
settings = OptionParser().get_default_values()
settings.update(settings_json, OptionParser())
document = new_document("", settings)
pyodide_analyzer = PyodideAnalyzer(inner_analyzer)
def test_flatten_suffix_tree():
t = SuffixTree()
d = {
("a", "b", "c"): 1,
("a", "b", "d"): 2,
("a", "d", "d"): 3,
("a", "x", "y"): 4,
("b", "x", "c"): 5,
("b", "x", "d"): 6,
("b", "y", "d"): 7,
}
t.add_many(d.items())
r = flatten_suffix_tree(t._tree)
assert d == r
class dummy_app:
_sphinxjs_analyzer = pyodide_analyzer
document = document
class dummy_state:
document = document
def test_pyodide_analyzer():
function_names = {x.name for x in pyodide_analyzer.js_docs["pyodide"]["function"]}
attribute_names = {x.name for x in pyodide_analyzer.js_docs["pyodide"]["attribute"]}
assert function_names == {
"checkInterrupt",
"isPyProxy",
"loadPackage",
"loadPackagesFromImports",
"mountNativeFS",
"pyimport",
"registerComlink",
"registerJsModule",
"runPython",
"runPythonAsync",
"setDefaultStdout",
"setInterruptBuffer",
"setStderr",
"setStdin",
"setStdout",
"toPy",
"unpackArchive",
"unregisterJsModule",
}
assert attribute_names == {
"ERRNO_CODES",
"FS",
"PATH",
"version",
"globals",
"loadedPackages",
"pyodide_py",
"version",
}
def test_content():
JsDocContent = get_jsdoc_content_directive(dummy_app)
a = JsDocContent.__new__(JsDocContent)
a.arguments = ["pyodide"]
a.state = dummy_state
def no_op_parse_rst(rst):
return rst
a.parse_rst = no_op_parse_rst
results = {}
for idx, entry in enumerate(a.run().split(".. js:")):
[first_line, _, body] = entry.partition("\n")
if "::" not in first_line:
continue
[directive, name] = first_line.split("::")
directive = directive.strip()
name = name.strip()
if directive == "module":
assert name == a.arguments[0]
continue
body = body.strip()
d = dict(idx=idx, directive=directive, body=body, sig="")
if "(" in name:
[name, sig] = name.split("(")
d["sig"] = sig
results[name] = d
rp = results["globals"]
assert rp["directive"] == "attribute"
assert rp["sig"] == ""
assert "An alias to the global Python namespace." in rp["body"]
rp = results["runPython"]
assert rp["directive"] == "function"
assert rp["sig"] == "code, options)"
assert "Runs a string of Python code from JavaScript" in rp["body"]
JsDocSummary = get_jsdoc_summary_directive(dummy_app)
jsdoc_summary = JsDocSummary.__new__(JsDocSummary)
jsdoc_summary.state = dummy_state
jsdoc_summary.options = {}
def test_extract_summary():
assert (
jsdoc_summary.extract_summary(
"Registers the Js object ``module`` as a Js module with ``name``. This module can then be imported from Python using the standard Python\nimport system. :func:`some_func`"
)
== "Registers the Js object ``module`` as a Js module with ``name``."
)
def test_summary():
globals = jsdoc_summary.get_summary_table(
"globalThis", dummy_app._sphinxjs_analyzer.js_docs["globalThis"]["function"]
)
attributes = jsdoc_summary.get_summary_table(
"pyodide", dummy_app._sphinxjs_analyzer.js_docs["pyodide"]["attribute"]
)
functions = jsdoc_summary.get_summary_table(
"pyodide", dummy_app._sphinxjs_analyzer.js_docs["pyodide"]["function"]
)
globals = {t[1]: t for t in globals}
attributes = {t[1]: t for t in attributes}
functions = {t[1]: t for t in functions}
assert globals["loadPyodide"] == (
"**async** ",
"loadPyodide",
"(options)",
"Load the main Pyodide wasm module and initialize it.",
"globalThis.loadPyodide",
)
assert attributes["pyodide_py"] == (
"",
"pyodide_py",
"",
"An alias to the Python :py:mod:`pyodide` package.",
"pyodide.pyodide_py",
)
assert attributes["version"] == (
"",
"version",
"",
"The Pyodide version.",
"pyodide.version",
)
assert attributes["loadedPackages"] == (
"",
"loadedPackages",
"",
"The list of packages that Pyodide has loaded.",
"pyodide.loadedPackages",
)
assert functions["loadPackagesFromImports"][:-2] == (
"**async** ",
"loadPackagesFromImports",
"(code, options)",
)
def test_type_name():
tn = inner_analyzer._type_name
assert tn({"name": "void", "type": "intrinsic"}) == ":js:data:`void`"
assert tn({"value": None, "type": "literal"}) == ":js:data:`null`"
assert (
tn(
{
"name": "Promise",
"type": "reference",
"typeArguments": [{"name": "string", "type": "intrinsic"}],
}
).strip()
== r":js:class:`Promise`\ **<**\ :js:data:`string`\ **>**"
)
assert (
tn(
{
"asserts": False,
"name": "jsobj",
"targetType": {"name": "PyProxy", "type": "reference"},
"type": "predicate",
}
).strip()
== ":js:data:`boolean` (typeguard for :js:class:`PyProxy`)"
)
assert (
tn(
{
"declaration": {
"kindString": "Method",
"name": "messageCallback",
"signatures": [
{
"kindString": "Call signature",
"name": "messageCallback",
"parameters": [
{
"flags": {},
"kindString": "Parameter",
"name": "message",
"type": {"name": "string", "type": "intrinsic"},
}
],
"type": {"name": "void", "type": "intrinsic"},
}
],
},
"type": "reflection",
}
).strip()
== r"\ **(**\ \ **message:** :js:data:`string`\ **) =>** :js:data:`void`"
)
assert (
tn(
{
"name": "Iterable",
"type": "reference",
"typeArguments": [
{
"elements": [
{
"element": {"name": "string", "type": "intrinsic"},
"isOptional": False,
"name": "key",
"type": "named-tuple-member",
},
{
"element": {"name": "any", "type": "intrinsic"},
"isOptional": False,
"name": "value",
"type": "named-tuple-member",
},
],
"type": "tuple",
}
],
}
).strip()
== r":js:data:`Iterable`\ **<**\ \ **[**\ \ **key:** :js:data:`string`\ **,** \ **value:** :js:data:`any`\ **]** \ **>**"
)
assert (
tn(
{
"declaration": {
"flags": {},
"indexSignature": {
"flags": {},
"kindString": "Index signature",
"parameters": [
{
"flags": {},
"name": "key",
"type": {"name": "string", "type": "intrinsic"},
}
],
"type": {"name": "string", "type": "intrinsic"},
},
"kindString": "Type literal",
},
"type": "reflection",
}
).strip()
== r"""\ **{**\ \ **[key:** :js:data:`string`\ **]:** :js:data:`string`\ **}**\ """.strip()
)
assert (
tn(
{
"declaration": {
"children": [
{
"flags": {},
"kindString": "Property",
"name": "cache",
"type": {"name": "PyProxyCache", "type": "reference"},
},
{
"flags": {"isOptional": True},
"kindString": "Property",
"name": "destroyed_msg",
"type": {"name": "string", "type": "intrinsic"},
},
{
"flags": {},
"kindString": "Property",
"name": "ptr",
"type": {"name": "number", "type": "intrinsic"},
},
],
"flags": {},
"kindString": "Type literal",
},
"type": "reflection",
}
).strip()
== r"""\ **{**\ \ **cache:** :js:class:`PyProxyCache`\ **,** \ **destroyed_msg?:** :js:data:`string`\ **,** \ **ptr:** :js:data:`number`\ **}**\ """.strip()
)