File: test_example.py

package info (click to toggle)
ford 7.0.12-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 15,000 kB
  • sloc: python: 11,852; f90: 419; javascript: 51; fortran: 45; makefile: 23
file content (531 lines) | stat: -rw-r--r-- 17,642 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
import shutil
import sys
import pathlib
import re
from urllib.parse import urlparse

import ford
from ford.graphs import graphviz_installed

from bs4 import BeautifulSoup
import pytest

from conftest import chdir


pytestmark = pytest.mark.filterwarnings("ignore::bs4.MarkupResemblesLocatorWarning")

HEADINGS = re.compile(r"h[1-4]")
ANY_TEXT = re.compile(r"h[1-4]|p")


def front_page_list(settings, items):
    max_frontpage_items = int(settings.max_frontpage_items)
    return sorted(items)[:max_frontpage_items]


@pytest.fixture(scope="module")
def example_project(tmp_path_factory):
    this_dir = pathlib.Path(__file__).parent
    tmp_path = tmp_path_factory.getbasetemp() / "example"
    shutil.copytree(this_dir / "../example", tmp_path)

    with pytest.MonkeyPatch.context() as m, chdir(tmp_path):
        m.setattr(sys, "argv", ["ford", "example-project-file.md"])
        ford.run()

    with open(tmp_path / "example-project-file.md", "r") as f:
        project_file = f.read()

    project_file, project_settings = ford.load_settings(project_file, tmp_path)
    settings, _ = ford.parse_arguments({}, project_file, project_settings, tmp_path)

    doc_path = tmp_path / "doc"

    return doc_path, settings


def read_html(filename):
    with open(filename, "r") as f:
        return BeautifulSoup(f.read(), features="html.parser")


@pytest.fixture(scope="module")
def example_index(example_project):
    path, settings = example_project
    index = read_html(path / "index.html")
    return index, settings


def test_nav_bar(example_index):
    index, settings = example_index

    navbar_links = index.nav("a")
    link_names = {link.text.strip() for link in navbar_links}

    expected_pages = {
        settings.project,
        "Notes",
        "Source Files",
        "Block Data",
        "Modules",
        "Procedures",
        "Derived Types",
        "Programs",
        "Abstract Interfaces",
        "Namelists",
    }
    assert expected_pages == link_names


def test_jumbotron(example_index):
    """This test will probably break if a different theme or HTML/CSS
    framework is used"""

    index, settings = example_index
    jumbotron = index.find("div", id="jumbotron")

    jumbotron_text = (p.text for p in jumbotron("p"))
    assert settings.summary in jumbotron_text

    links = [link["href"] for link in jumbotron("a")]

    for location_link in [
        "project_bitbucket",
        "project_download",
        "project_github",
        "project_gitlab",
        "project_sourceforge",
        "project_website",
    ]:
        if (link := getattr(settings, location_link)) is not None:
            assert link in links


def test_developer_info_box(example_index):
    index, settings = example_index

    # Assume that we have something like:
    #     `<div><h2>Developer Info</h2> .. box </div>`
    developer_info = index.find(string="Developer Info").parent.parent

    dev_text = [tag.text for tag in developer_info(ANY_TEXT)]

    assert settings.author in dev_text
    assert settings.author_description in dev_text


def test_latex(example_index):
    index, settings = example_index

    tex_tags = index("script", type=re.compile("math/tex.*"))

    assert len(tex_tags) == 4


def test_source_file_links(example_index):
    index, settings = example_index

    source_files_box = index.find(ANY_TEXT, string="Source Files").parent
    source_files_list = sorted([f.text for f in source_files_box("li")])

    assert source_files_list == front_page_list(
        settings,
        [
            "ford_test_module.fpp",
            "ford_test_program.f90",
            "ford_example_type.f90",
            "ford_interfaces.f90",
            "ford_f77_example.f",
        ],
    )


def test_module_links(example_index):
    index, settings = example_index

    modules_box = index.find(ANY_TEXT, string="Modules").parent
    modules_list = sorted([f.text for f in modules_box("li")])

    assert modules_list == front_page_list(
        settings, ["test_module", "ford_example_type_mod", "interfaces"]
    )


def test_procedures_links(example_index):
    index, settings = example_index

    proceduress_box = index.find(ANY_TEXT, string="Procedures").parent
    proceduress_list = sorted([f.text for f in proceduress_box("li")])

    procedures = front_page_list(
        settings,
        [
            "decrement",
            "do_foo_stuff",
            "do_stuff",
            "increment",
            "check",
            "apply_check",
            "higher_order_unary_f",
        ],
    )
    assert proceduress_list == procedures


def test_types_links(example_index):
    index, settings = example_index

    types_box = index.find(ANY_TEXT, string="Derived Types").parent
    types_list = sorted([f.text for f in types_box("li")])

    assert types_list == sorted(["bar", "foo", "example_type", "say_type_base"])


def test_types_type_bound_procedure(example_project):
    path, _ = example_project
    index = read_html(path / "type/example_type.html")

    bound_procedures_section = index.find("h2", string="Type-Bound Procedures").parent

    assert "This will document" in bound_procedures_section.text, "Binding summary"
    assert (
        "This binding has more documentation" in bound_procedures_section.text
    ), "Binding full docstring"
    assert (
        "Prints how many times" in bound_procedures_section.ul.text
    ), "Full procedure summary"
    assert (
        "This subroutine has more documentation" in bound_procedures_section.ul.text
    ), "Full procedure full docstring"


def test_types_constructor_summary(example_project):
    path, _ = example_project
    index = read_html(path / "type/example_type.html")

    constructor_section = index.find("h2", string="Constructor").parent

    assert "This is a constructor for our type" in constructor_section.text
    assert "This constructor has more documentation" in constructor_section.text
    assert "specific constructor" in constructor_section.ul.text
    assert "More documentation" in constructor_section.ul.text


def test_types_constructor_page(example_project):
    path, _ = example_project
    index = read_html(path / "interface/example_type.html")

    constructor_section = index.find("h2", string=re.compile("example_type")).parent

    assert "This is a constructor for our type" in constructor_section.text
    assert "This constructor has more documentation" in constructor_section.text
    assert "specific constructor" in constructor_section.text
    assert "More documentation" in constructor_section.text


def test_types_finaliser(example_project):
    path, _ = example_project
    index = read_html(path / "type/example_type.html")

    finaliser_section = index.find("h2", string="Finalization Procedures").parent

    assert "This is the finaliser" in finaliser_section.text
    assert "This finaliser has more documentation" in finaliser_section.text
    assert "Cleans up" in finaliser_section.ul.text
    assert "More documentation" in finaliser_section.ul.text


@pytest.mark.skipif(not graphviz_installed, reason="Requires graphviz")
def test_graph_submodule(example_project):
    path, _ = example_project
    index = read_html(path / "module/test_submodule.html")

    graph_nodes = index.svg.find_all("g", class_="node")

    assert len(graph_nodes) == 2
    titles = sorted([node.find("text").text for node in graph_nodes])
    assert titles == sorted(["test_module", "test_submodule"])


def test_procedure_return_value(example_project):
    path, _ = example_project
    index = read_html(path / "proc/multidimension_string.html")

    retvar = index.find(string=re.compile("Return Value")).parent
    assert (
        "character(kind=kind('a'), len=4), dimension(:, :), allocatable" in retvar.text
    )


def test_info_bar(example_project):
    path, _ = example_project
    index = read_html(path / "proc/decrement.html")

    info_bar = index.find(id="info-bar")
    assert "creativecommons" in info_bar.find(id="meta-license").a["href"]
    assert "of total for procedures" in info_bar.find(id="statements").a["title"]
    assert "4 statements" in info_bar.find(id="statements").a.text

    breadcrumb = info_bar.find(class_="breadcrumb")
    assert len(breadcrumb("li")) == 3
    breadcrumb_text = [crumb.text for crumb in breadcrumb("li")]
    assert breadcrumb_text == ["ford_test_module.fpp", "test_module", "decrement"]


def test_side_panel(example_project):
    path, _ = example_project
    index = read_html(path / "program/ford_test_program.html")

    side_panel = index.find(id="sidebar")
    assert "None" not in side_panel.text

    side_panels = side_panel.find_all(class_="card-header")
    assert len(side_panels) == 4

    variables_panel = side_panels[0].parent.parent
    assert len(variables_panel("a")) == 2
    assert variables_panel.a.text.strip() == "Variables"
    variables_anchor_link = variables_panel("a")[1]
    assert variables_anchor_link.text.strip() == "global_pi"
    assert (
        variables_anchor_link["href"]
        == "../program/ford_test_program.html#variable-global_pi"
    )

    subroutines_panel = side_panels[3].parent.parent
    assert len(subroutines_panel("a")) == 4
    assert subroutines_panel.a.text.strip() == "Subroutines"
    subroutines_anchor_link = subroutines_panel("a")[1]
    assert subroutines_anchor_link.text.strip() == "do_foo_stuff"
    assert (
        subroutines_anchor_link["href"]
        == "../program/ford_test_program.html#proc-do_foo_stuff"
    )

    type_index = read_html(path / "type/example_type.html")
    constructor_panel = type_index.find(id="cons-0")
    assert constructor_panel.a.text.strip() == "example_type"
    assert (
        constructor_panel.a["href"]
        == "../type/example_type.html#interface-example_type"
    )
    finaliser_panel = type_index.find(id="fins-0")
    assert finaliser_panel.a.text.strip() == "example_type_finalise"
    assert (
        finaliser_panel.a["href"]
        == "../type/example_type.html#finalproc-example_type_finalise"
    )

    check_index = read_html(path / "interface/check.html")
    check_sidebar = check_index.find(id="sidebar")
    assert "None" in check_sidebar.text.strip()
    assert check_sidebar.find_all(class_="card-primary") == []


def test_variable_lists(example_project):
    path, _ = example_project
    index = read_html(path / "program/ford_test_program.html")

    varlist = index.find(class_="varlist")
    assert "Type" in varlist.thead.text
    assert "Attributes" in varlist.thead.text
    assert "Name" in varlist.thead.text
    assert "Initial" in varlist.thead.text
    assert "Optional" not in varlist.thead.text
    assert "Intent" not in varlist.thead.text

    assert len(varlist("tr")) == 2
    assert varlist.tbody.tr.find(class_="anchor")["id"] == "variable-global_pi"
    expected_declaration = "real(kind=real64) :: global_pi = acos(-1) a global variable, initialized to the value of pi This is a doxygen comment for global pi"
    declaration_no_whitespace = varlist.tbody.text.replace("\n", "").replace(" ", "")
    assert declaration_no_whitespace == expected_declaration.replace(" ", "")


def test_deprecated(example_project):
    path, _ = example_project
    index = read_html(path / "module/test_module.html")

    apply_check_box = index.find(id="proc-apply_check").parent
    assert apply_check_box.h3.span.text == "Deprecated"


def test_private_procedure_links(example_project):
    path, _ = example_project
    index = read_html(path / "type/example_type.html")

    subroutine_box = index.find("h3", string=re.compile("example_type_say"))
    assert subroutine_box.a is None


def test_public_procedure_links(example_project):
    path, _ = example_project
    index = read_html(path / "module/test_module.html")

    subroutine_box = index.find(id="proc-increment").parent
    assert subroutine_box.a is not None
    assert subroutine_box.a["href"] == "../proc/increment.html"


def test_all_internal_links_resolve(example_project):
    """Opens every HTML file, finds all internal links and checks that
    they resolve to files that actually exist. Furthermore, if the
    link has a fragment ("#something"), check that that fragment
    exists in the specified file.

    """

    path, _ = example_project
    html_files = {}

    for html in path.glob("**/*.html"):
        with open(html, "r") as f:
            html_files[html] = BeautifulSoup(f.read(), features="html.parser")

    for html, index in html_files.items():
        for a_tag in index("a"):
            link = urlparse(a_tag.get("href", ""))
            if link.netloc or link.scheme == "mailto" or not link.path:
                continue

            assert not link.path.startswith(
                "/"
            ), f"absolute path in {a_tag} on page {html}"

            link_path = (html.parent / link.path).resolve()
            assert link_path.exists(), f"{a_tag} on page {html}"

            if not link.fragment:
                continue

            # Check that fragments resolve too
            index2 = html_files[link_path]
            assert index2.find("a", href=re.compile(link.fragment)), html


def test_submodule_procedure_implementation_links(example_project):
    path, _ = example_project
    module_index = read_html(path / "module/test_module.html")

    interfaces_section = module_index.find("h2", string="Interfaces").parent
    check_heading = interfaces_section.ul.li
    assert "subroutine check" in check_heading.text
    implementation_link = check_heading.a
    assert implementation_link["href"].endswith("proc/check.html")

    proc_index = read_html(path / "proc/check.html")
    check_impl_heading = proc_index.find(string=re.compile("subroutine +check")).parent
    assert check_impl_heading.text.startswith("module subroutine check")
    check_interface_link = check_impl_heading.a
    assert "Interface" in check_interface_link.text
    assert check_interface_link["href"].endswith("interface/check.html")

    interface_index = read_html(path / "interface/check.html")
    check_interface_heading = interface_index.find(
        string=re.compile("subroutine +check")
    ).parent
    assert check_interface_heading.text.startswith("public module subroutine check")
    check_impl_link = check_interface_heading.a
    assert "Implementation" in check_impl_link.text
    assert check_impl_link["href"].endswith("proc/check.html")


def test_interfaces(example_project):
    path, _ = example_project
    interface_mod_page = read_html(path / "module/interfaces.html")

    # Span inside the div we actually want, but this has an id
    box_span = interface_mod_page.find(id="interface-generic_unary_f")
    assert box_span

    box = box_span.parent.parent

    list_items = box("li")
    list_item_titles = sorted([li.h3.text.strip() for li in list_items])
    assert list_item_titles == sorted(
        [
            "public pure function real_unary_f(x)",
            "public pure function higher_order_unary_f(f, n)",
            "Dummy Procedures and Procedure Pointers",
        ]
    )

    assert len(list_items[-1]("tr")) == 2


def test_static_pages(example_project):
    path, _ = example_project
    notes_page = read_html(path / "page/index.html")

    # We should have some "author" metadata, but no date
    assert notes_page.find(id="author").text.strip() == "Jane Bloggs", "author"
    assert notes_page.find(id="date") is None, "date"

    sidebar = notes_page.find(id="sidebar-toc")
    subpage_links = sidebar.find_all("a")

    # Check the links in the sidebare to subpages is correct

    subpage_names = [link.text for link in subpage_links]
    # Note that order matters here, so no `sorted`!
    expected_subpage_names = [
        "Notes",
        "First page",
        "Second page",
        "Subdirectories",
        "Subpage in subdirectory",
        "Yet Another Page",
    ]
    assert subpage_names == expected_subpage_names, "subpages"

    subpage_paths = [pathlib.Path(link["href"]) for link in subpage_links]
    expected_subpage_paths = [
        pathlib.Path(f"{link}.html")
        for link in (
            "index",
            "subpage2",
            "subpage1",
            "subdir/index",
            "subdir/subsubpage",
            "subpage3",
        )
    ]
    assert subpage_paths == expected_subpage_paths, "ordered_subpages"


def test_namelist_lists(example_project):
    path, _ = example_project
    namelist_lists = read_html(path / "lists/namelists.html")

    table = namelist_lists.table
    assert table.a.text == "example_namelist"
    assert table.a["href"] == "../namelist/example_namelist.html"


def test_namelist_page(example_project):
    path, _ = example_project
    namelist_page = read_html(path / "namelist/example_namelist.html")

    table = namelist_page.table
    # First row is header, skip it
    rows = table.find_all("tr")[1:]
    variables = sorted([row.td.text for row in rows])
    expected_variables = sorted(["input", "module_level_variable", "local_variable"])

    assert variables == expected_variables


def test_linking_to_page_alias_from_nested_page(example_project):
    path, _ = example_project
    subsubpage = read_html(path / "page/subdir/subsubpage.html")

    link = subsubpage.find("a", string=re.compile("such as"))
    assert link["href"] == "../subpage1.html"


def test_type_attributes(example_project):
    path, _ = example_project
    type_page = read_html(path / "type/say_type_base.html")

    def_statement = type_page.find(id="type-def-statement")
    assert def_statement.text.strip() == "type, public, abstract :: say_type_base"