WillTools 2026-09-09 -- clean snapshot of the development repo for review
ci / test (push) Successful in 6m18s
ci / test (push) Successful in 6m18s
History-free snapshot of the private development repository at cbe45c5 (2026-09-09). Code, docs and public catalogue only; no plant or job knowledge layers, no client or job names (tools/scrub.py clean). The intro deck is left out of this snapshot because its slide images could not be checked for job drawings.
This commit is contained in:
@@ -0,0 +1,490 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Tests for `tools/ba_pagespec.py` -- the job <-> values half AND the
|
||||
page-spec builder of the Beam Analysis page.
|
||||
|
||||
Loaded by path, exactly like `test_deckcheck.py` / `test_snapshot_defer.py`
|
||||
beside it: this is host CPython code sitting in a repository whose extension
|
||||
is IronPython 2.7, so `tools/` is put on `sys.path` and the module is
|
||||
imported directly (it has no `vibe` dependency at all; it imports
|
||||
`beamcalc.model` the plain way, falling back to `beamcalc/src` on
|
||||
`sys.path`).
|
||||
|
||||
What this suite checks, per the ba-web-spec brief:
|
||||
|
||||
1. the round trip (spec section 1) on both fixtures in
|
||||
`beamcalc/fixtures/` -- `from_job(job_from_values(values_from_job(J)[0]))`
|
||||
equals `from_job(J)` field by field (`beam_equal == []`);
|
||||
2. every section-3 table appears in the values of the RBA1 fixture with at
|
||||
least one row, and row 1 carries every declared column key;
|
||||
3. a non-numeric `extents.length_ft` raises `ValueError` naming the key;
|
||||
4. a blank `settings.phi.shear` in values yields a job without that key,
|
||||
and `from_job` then applies its own `phi_shear == 0.75`;
|
||||
5. the page-spec builder (`build_pagespec` / `page_<id>`, spec sections
|
||||
1-4) -- the five pages in order with no page problems, every `field`
|
||||
key in `values`, every `TABLES` table registered once with its declared
|
||||
columns, every drawing `depends_on` resolvable, the L-section required
|
||||
fields, and the `main()` CLI path.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(
|
||||
os.path.abspath(__file__))))
|
||||
TOOLS = os.path.join(ROOT, 'tools')
|
||||
BEAMCALC_SRC = os.path.join(ROOT, 'beamcalc', 'src')
|
||||
for p in (TOOLS, BEAMCALC_SRC):
|
||||
if p not in sys.path:
|
||||
sys.path.insert(0, p)
|
||||
|
||||
import beamcalc.model as BM # noqa: E402
|
||||
ba_pagespec = pytest.importorskip('ba_pagespec') # noqa: E402
|
||||
P = ba_pagespec
|
||||
|
||||
FIXTURES = os.path.join(ROOT, 'beamcalc', 'fixtures')
|
||||
FIXTURE_FILES = ('rba5_16x22.json', 'rba1_8x48.json')
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ helpers
|
||||
|
||||
def _load_fixture(name):
|
||||
with open(os.path.join(FIXTURES, name), 'r', encoding='utf-8') as fh:
|
||||
return json.load(fh)
|
||||
|
||||
|
||||
def _round_trip_beam(fixture_name):
|
||||
"""`from_job(job_from_values(values_from_job(J)[0]))` for a fixture."""
|
||||
job = _load_fixture(fixture_name)
|
||||
values, defaults_why = P.values_from_job(job)
|
||||
rebuilt = P.job_from_values(values)
|
||||
return BM.from_job(job), BM.from_job(rebuilt), values, defaults_why
|
||||
|
||||
|
||||
def _walk_blocks(blocks, kind, out):
|
||||
"""Every block of `kind`, recursing into `columns` and `section` the
|
||||
way the front end's renderer does."""
|
||||
for b in blocks:
|
||||
if not isinstance(b, dict):
|
||||
continue
|
||||
if b.get('kind') == kind:
|
||||
out.append(b)
|
||||
elif b.get('kind') == 'columns':
|
||||
for col in (b.get('columns') or []):
|
||||
_walk_blocks(col, kind, out)
|
||||
elif b.get('kind') == 'section':
|
||||
_walk_blocks(b.get('blocks') or [], kind, out)
|
||||
|
||||
|
||||
def _all_field_keys(blocks):
|
||||
out = []
|
||||
_walk_blocks(blocks, 'field', out)
|
||||
return [b['key'] for b in out if b.get('key')]
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ tests
|
||||
|
||||
def test_round_trip_rba5():
|
||||
original, rebuilt, values, defaults_why = _round_trip_beam(
|
||||
'rba5_16x22.json')
|
||||
assert P.beam_equal(original, rebuilt) == []
|
||||
|
||||
|
||||
def test_round_trip_rba1():
|
||||
original, rebuilt, values, defaults_why = _round_trip_beam(
|
||||
'rba1_8x48.json')
|
||||
assert P.beam_equal(original, rebuilt) == []
|
||||
|
||||
|
||||
@pytest.mark.parametrize('fixture_name', FIXTURE_FILES)
|
||||
def test_defaults_why_is_populated(fixture_name):
|
||||
"""The flatten must have filled *something* from a model.py default --
|
||||
neither fixture carries every key (settings, ec_ksi, strand keys, ...)."""
|
||||
_, _, values, defaults_why = _round_trip_beam(fixture_name)
|
||||
assert defaults_why, \
|
||||
'expected defaults_why to name at least one model.py default'
|
||||
for key, why in defaults_why.items():
|
||||
assert isinstance(key, str) and key
|
||||
assert isinstance(why, str) and why
|
||||
|
||||
|
||||
def test_every_table_has_row_1_with_all_declared_columns_in_rba1():
|
||||
"""Every section-3 table appears in the RBA1 fixture's values with at
|
||||
least one present row, and row 1 carries every declared column key
|
||||
(ba-web-spec.md section 3)."""
|
||||
job = _load_fixture('rba1_8x48.json')
|
||||
values, _ = P.values_from_job(job)
|
||||
for name, meta in P.TABLES.items():
|
||||
assert values.get('{0}.1.present'.format(name)) == 'true', \
|
||||
'table {0} has no present row 1 in the RBA1 values'.format(name)
|
||||
for col, typ, default in meta['columns']:
|
||||
key = '{0}.1.{1}'.format(name, col)
|
||||
assert key in values, \
|
||||
'row 1 of table {0} is missing declared column {1!r}'.format(
|
||||
name, col)
|
||||
assert isinstance(values[key], str), \
|
||||
'cell {0} is not a string'.format(key)
|
||||
|
||||
|
||||
def test_job_from_values_rejects_non_numeric_length():
|
||||
values, _ = P.values_from_job(_load_fixture('rba5_16x22.json'))
|
||||
values['extents.length_ft'] = 'abc'
|
||||
with pytest.raises(ValueError) as exc:
|
||||
P.job_from_values(values)
|
||||
assert str(exc.value).startswith('extents.length_ft')
|
||||
|
||||
|
||||
def test_tapered_line_load_round_trips_after_page_edit():
|
||||
"""A page edit that makes a line load's end differ from its start (a
|
||||
taper, model.py:646-649) must rebuild as an explicit [start, end]
|
||||
pair, not two scalar keys the engine would misread."""
|
||||
job = _load_fixture('rba5_16x22.json')
|
||||
values, _ = P.values_from_job(job)
|
||||
# L1 carries deadc: 3.05 (uniform). Edit it into a taper.
|
||||
assert values['line_loads.1.deadc_start'] == '3.05'
|
||||
values['line_loads.1.deadc_end'] = '4.0'
|
||||
rebuilt = P.job_from_values(values)
|
||||
l1 = rebuilt['loads']['line'][0]
|
||||
assert l1['deadc'] == [3.05, 4.0]
|
||||
beam = BM.from_job(rebuilt)
|
||||
assert beam.line_loads[0].cases['deadc'] == (3.05, 4.0)
|
||||
|
||||
|
||||
def test_blank_phi_shear_omits_key_and_default_applies():
|
||||
"""A blank `settings.phi.shear` yields a job without that key, and
|
||||
`from_job` then applies its own default (Settings.phi_shear = 0.75,
|
||||
model.py:162)."""
|
||||
job = _load_fixture('rba5_16x22.json')
|
||||
values, _ = P.values_from_job(job)
|
||||
assert values['settings.phi.shear'] == '0.75'
|
||||
values['settings.phi.shear'] = ''
|
||||
rebuilt = P.job_from_values(values)
|
||||
assert 'shear' not in rebuilt['settings']['phi']
|
||||
beam = BM.from_job(rebuilt)
|
||||
assert beam.settings.phi_shear == 0.75
|
||||
|
||||
|
||||
# ----------------------------------------------------- the page-spec builder
|
||||
#
|
||||
# `build_pagespec(reads=<fixture job>)` -- a fixture job is a legal reads
|
||||
# dict (a partial job, ba-web-spec.md section 6). These are the five checks
|
||||
# the task brief names.
|
||||
|
||||
def _build_spec_from_fixture(fixture_name='rba5_16x22.json'):
|
||||
return P.build_pagespec(values=None, reads=_load_fixture(fixture_name))
|
||||
|
||||
|
||||
def test_build_pagespec_has_five_pages_in_order_with_no_problems():
|
||||
"""(1) `build_pagespec(reads=<fixture>)`: five pages, ids in the fixed
|
||||
order (spec section 2), no page problems, and every `field` block's key
|
||||
present in `values`."""
|
||||
spec = _build_spec_from_fixture()
|
||||
ids = [p['id'] for p in spec['pages']]
|
||||
assert ids == ['extents', 'materials', 'loads', 'reinforcement',
|
||||
'handling']
|
||||
for page in spec['pages']:
|
||||
assert page['problems'] == [], (
|
||||
'page {0!r} has problems: {1!r}'.format(page['id'],
|
||||
page['problems']))
|
||||
for key in _all_field_keys(page['blocks']):
|
||||
assert key in spec['values'], \
|
||||
'page {0!r} field {1!r} is missing from values'.format(
|
||||
page['id'], key)
|
||||
|
||||
|
||||
def test_build_pagespec_blank_never_raises_and_round_trips_json():
|
||||
"""The no-Revit path (`values=None`, `reads={}`) -- a complete,
|
||||
defaulted spec that survives `dumps`/`json.loads`."""
|
||||
spec = P.build_pagespec(values=None, reads={})
|
||||
assert spec['schema'] == 1
|
||||
assert spec['title'] == 'Beam Analysis'
|
||||
assert len(spec['pages']) == 5
|
||||
assert all(not p['problems'] for p in spec['pages'])
|
||||
blob = P.dumps(spec)
|
||||
back = json.loads(blob)
|
||||
assert back['schema'] == 1 and back['title'] == 'Beam Analysis'
|
||||
|
||||
|
||||
def test_basis_and_defaults_why_are_filtered_to_rendered_keys():
|
||||
"""The fixture carries no `basis`, so a synthetic one must land only on
|
||||
values keys this spec renders; `defaults_why` comes from
|
||||
`values_from_job` and must likewise be a subset of values."""
|
||||
job = _load_fixture('rba5_16x22.json')
|
||||
job['basis'] = {'extents.length_ft': 'the Length parameter',
|
||||
'not.a.key': 'should be filtered out'}
|
||||
spec = P.build_pagespec(values=None, reads=job)
|
||||
assert spec['basis'].get('extents.length_ft') == 'the Length parameter'
|
||||
assert 'not.a.key' not in spec['basis']
|
||||
for key in spec['basis']:
|
||||
assert key in spec['values']
|
||||
for key in spec['defaults_why']:
|
||||
assert key in spec['values']
|
||||
|
||||
|
||||
def test_every_table_is_registered_once_with_declared_columns():
|
||||
"""(2) Every table in TABLES appears exactly once in `spec['tables']`,
|
||||
carrying the declared columns in order -- the de-duped top-level dict
|
||||
(pa-web-spec.md section 4) mirrors what the page blocks show."""
|
||||
spec = _build_spec_from_fixture()
|
||||
for name, meta in P.TABLES.items():
|
||||
assert name in spec['tables'], \
|
||||
'table {0} is missing from spec[tables]'.format(name)
|
||||
cols = spec['tables'][name]['columns']
|
||||
assert [c['key'] for c in cols] == [c[0] for c in meta['columns']], \
|
||||
'table {0} columns do not match TABLES'.format(name)
|
||||
for c in cols:
|
||||
assert c['label'] and 'editor' in c
|
||||
if c['label'].endswith(')'):
|
||||
assert c['unit'], \
|
||||
'column {0}.{1} label has a unit but unit is null'.format(
|
||||
name, c['key'])
|
||||
# each table is named by exactly one editable_table block in the pages
|
||||
found = {}
|
||||
for page in spec['pages']:
|
||||
blocks = []
|
||||
_walk_blocks(page['blocks'], 'editable_table', blocks)
|
||||
for b in blocks:
|
||||
found[b['table']] = found.get(b['table'], 0) + 1
|
||||
assert sorted(found) == sorted(P.TABLES)
|
||||
assert all(n == 1 for n in found.values())
|
||||
|
||||
|
||||
def test_drawing_depends_on_keys_resolve():
|
||||
"""(3) Each `drawing` block's `depends_on` entries exist in values or
|
||||
name a row table (a prefix that redraws on any of its cells)."""
|
||||
spec = _build_spec_from_fixture()
|
||||
drawings = []
|
||||
for page in spec['pages']:
|
||||
_walk_blocks(page['blocks'], 'drawing', drawings)
|
||||
assert drawings, 'expected drawing blocks across the spec'
|
||||
for b in drawings:
|
||||
assert b['drawing_key'] in spec['geometry'], \
|
||||
'{0} missing from geometry'.format(b['drawing_key'])
|
||||
for dep in b['depends_on']:
|
||||
assert dep in spec['values'] or dep in P.TABLES, \
|
||||
'drawing {0} depends on unknown key/table {1!r}'.format(
|
||||
b['drawing_key'], dep)
|
||||
|
||||
|
||||
def test_l_section_marks_stem_width_required():
|
||||
"""(4) A reads dict whose section type is 'L' marks `stem_width_in`
|
||||
(and the other stem/flange fields) required (spec section 3: they
|
||||
"render on every type but are required only when the type needs them",
|
||||
model.py:562-567)."""
|
||||
job = _load_fixture('rba5_16x22.json')
|
||||
job['extents']['section'] = {
|
||||
'type': 'L', 'side': 'left',
|
||||
'stem_width_in': 6, 'stem_height_in': 16,
|
||||
'flange_width_in': 10, 'flange_thickness_in': 6}
|
||||
spec = P.build_pagespec(values=None, reads=job)
|
||||
fields = {}
|
||||
for page in spec['pages']:
|
||||
if page['id'] != 'extents':
|
||||
continue
|
||||
blocks = []
|
||||
_walk_blocks(page['blocks'], 'field', blocks)
|
||||
for b in blocks:
|
||||
fields[b['key']] = b
|
||||
assert fields['extents.section.stem_width_in']['required'] is True
|
||||
assert fields['extents.section.stem_height_in']['required'] is True
|
||||
assert fields['extents.section.flange_width_in']['required'] is True
|
||||
assert fields['extents.section.flange_thickness_in']['required'] is True
|
||||
# the overall width/height stay required on every type
|
||||
assert fields['extents.section.width_in']['required'] is True
|
||||
assert fields['extents.section.height_in']['required'] is True
|
||||
|
||||
|
||||
def test_rectangular_reads_do_not_require_stem_or_flange():
|
||||
job = _load_fixture('rba5_16x22.json')
|
||||
spec = P.build_pagespec(values=None, reads=job)
|
||||
for page in spec['pages']:
|
||||
if page['id'] != 'extents':
|
||||
continue
|
||||
blocks = []
|
||||
_walk_blocks(page['blocks'], 'field', blocks)
|
||||
by_key = {b['key']: b for b in blocks}
|
||||
for key in ('extents.section.stem_width_in',
|
||||
'extents.section.stem_height_in',
|
||||
'extents.section.flange_width_in',
|
||||
'extents.section.flange_thickness_in',
|
||||
'extents.section.side'):
|
||||
assert by_key[key]['required'] is False, key
|
||||
|
||||
|
||||
def test_required_keys_present():
|
||||
"""The spec's required fields exist and are marked: length,
|
||||
section width/height, materials.fc_ksi (spec section 2)."""
|
||||
spec = _build_spec_from_fixture()
|
||||
required = {}
|
||||
for page in spec['pages']:
|
||||
blocks = []
|
||||
_walk_blocks(page['blocks'], 'field', blocks)
|
||||
for b in blocks:
|
||||
if b.get('required'):
|
||||
required[b['key']] = b
|
||||
for key in ('extents.length_ft', 'extents.section.width_in',
|
||||
'extents.section.height_in', 'materials.fc_ksi'):
|
||||
assert key in required, key
|
||||
assert required['materials.fc_ksi']['required'] is True
|
||||
|
||||
|
||||
# ------------------------------------------------- the geometry dict
|
||||
#
|
||||
# `ba_pagespec.geometry(job)` (ba-web-spec.md section 4) -- the four
|
||||
# drawing entries the spec lists, built off `beamcalc.model.from_job(job)`
|
||||
# so every position comes from the code the engine runs.
|
||||
|
||||
GEO_KEYS = ('extents.elevation', 'loads.elevation', 'reinforcement.section',
|
||||
'reinforcement.elevation')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('fixture_name', FIXTURE_FILES)
|
||||
def test_geometry_fixture_entries_complete_and_clean(fixture_name):
|
||||
"""(1) `geometry(rba5)` and `geometry(rba1)`: all four entries present,
|
||||
every `problems` == [], supports count 2 with `x_ft` matching
|
||||
`Support.x_in / 12`, and the bar count equals sum(rows*cols) over the
|
||||
bar rows with every bar strictly inside the section."""
|
||||
job = _load_fixture(fixture_name)
|
||||
beam = BM.from_job(job)
|
||||
geo = P.geometry(job)
|
||||
|
||||
for key in GEO_KEYS:
|
||||
assert key in geo, 'geometry is missing the {0} entry'.format(key)
|
||||
assert isinstance(geo[key], dict)
|
||||
assert geo[key]['problems'] == [], \
|
||||
'{0} has unexpected problems: {1!r}'.format(key,
|
||||
geo[key]['problems'])
|
||||
|
||||
for key in ('extents.elevation', 'loads.elevation'):
|
||||
elev = geo[key]
|
||||
assert elev['length_ft'] == round(beam.length_ft, 3)
|
||||
assert elev['height_in'] == beam.section.height_in
|
||||
supports = elev['supports']
|
||||
assert len(supports) == 2
|
||||
for got, sup in zip(supports, beam.supports):
|
||||
assert got['x_ft'] == round(sup.x_in / 12.0, 3), \
|
||||
'support x_ft does not match Support.x_in / 12'
|
||||
assert got['length_ft'] == round(sup.length_in / 12.0, 3)
|
||||
assert got['kind'] == sup.kind
|
||||
|
||||
sec = geo['reinforcement.section']
|
||||
assert sec['type'] == beam.section.kind
|
||||
assert sec['width_in'] == beam.section.width_in
|
||||
assert sec['height_in'] == beam.section.height_in
|
||||
expected_bars = sum(row.rows * row.cols for row in beam.bars)
|
||||
assert len(sec['bars']) == expected_bars, (
|
||||
'section bar count {0} != sum(rows*cols) = {1}'.format(
|
||||
len(sec['bars']), expected_bars))
|
||||
assert len(sec['strands']) == sum(g.rows * g.cols for g in beam.strands)
|
||||
for bar in sec['bars']:
|
||||
assert 0.0 < bar['x_in'] < sec['width_in'], \
|
||||
'bar x_in {0} outside the section'.format(bar)
|
||||
assert 0.0 < bar['y_in'] < sec['height_in'], \
|
||||
'bar y_in {0} outside the section'.format(bar)
|
||||
for strand in sec['strands']:
|
||||
assert 0.0 < strand['x_in'] < sec['width_in']
|
||||
assert 0.0 < strand['y_in'] < sec['height_in']
|
||||
|
||||
elev = geo['reinforcement.elevation']
|
||||
assert elev['length_ft'] == round(beam.length_ft, 3)
|
||||
assert len(elev['bars']) == len(beam.bars)
|
||||
assert len(elev['transverse']) == sum(
|
||||
2 if z.symmetric else 1 for z in beam.transverse)
|
||||
|
||||
|
||||
def test_geometry_individual_bar_centres_match_the_engine():
|
||||
"""Every drawn bar centre must equal the centre `BarRow.bars(section)`
|
||||
computes (model.py:366-380) -- the same even left/right/top/bottom
|
||||
spacing the engine uses -- never a second, drifting spread."""
|
||||
job = _load_fixture('rba1_8x48.json')
|
||||
beam = BM.from_job(job)
|
||||
sec = P.geometry(job)['reinforcement.section']
|
||||
assert sec['width_in'] == beam.section.width_in
|
||||
assert sec['height_in'] == beam.section.height_in
|
||||
drawn = [(b['x_in'], b['y_in'], b['dia_in']) for b in sec['bars']]
|
||||
engine = []
|
||||
for row in beam.bars:
|
||||
area = row.bar_area
|
||||
db = BM.BAR_DIA.get(row.size, math.sqrt(4.0 * area / math.pi))
|
||||
for x, y, _a, _d in row.bars(beam.section):
|
||||
engine.append((x, y, db))
|
||||
assert drawn == engine
|
||||
# the fixture carries no custom bar areas, so every size is in the table
|
||||
for b in sec['bars']:
|
||||
assert b['dia_in'] == BM.BAR_DIA[b['size']]
|
||||
|
||||
|
||||
def test_geometry_point_load_beyond_the_beam_is_absent_and_named():
|
||||
"""(2) A point load beyond the beam length is absent from
|
||||
`loads.elevation.point` and named in that entry's problems."""
|
||||
job = _load_fixture('rba5_16x22.json')
|
||||
beam = BM.from_job(job)
|
||||
job['loads']['point'].append(
|
||||
{'id': 'P9', 'x_in': beam.length_in + 60.0, 'ecc_in': 0.0,
|
||||
'deadc': 5.0})
|
||||
geo = P.geometry(job)
|
||||
for key in GEO_KEYS:
|
||||
assert key in geo
|
||||
elev = geo['loads.elevation']
|
||||
ids = [p['id'] for p in elev['point']]
|
||||
assert 'P9' not in ids
|
||||
assert any('P9' in sentence for sentence in elev['problems'])
|
||||
# the in-beam load from the fixture is still drawn
|
||||
assert 'P1' in ids
|
||||
# the out-of-beam load leaves the other entries' problems untouched
|
||||
assert geo['extents.elevation']['problems'] == []
|
||||
|
||||
|
||||
def test_geometry_empty_job_returns_four_entries_with_problems():
|
||||
"""(3) `geometry({'schema': 'beamcalc/1'})` -- nothing else -- returns
|
||||
all four entries with non-empty problems and does not raise (a model
|
||||
whose reads carry only the schema must never take the page down)."""
|
||||
geo = P.geometry({'schema': 'beamcalc/1'})
|
||||
for key in GEO_KEYS:
|
||||
assert key in geo
|
||||
assert isinstance(geo[key], dict)
|
||||
assert geo[key]['problems'], \
|
||||
'{0} expected a problems sentence for the empty job'.format(key)
|
||||
assert 'from_job' in geo[key]['problems'][0]
|
||||
|
||||
|
||||
def test_main_writes_a_parsing_spec_json():
|
||||
"""(5) `python tools/ba_pagespec.py --out <tmp>` exits 0 and the JSON
|
||||
parses -- the CLI no-Revit path."""
|
||||
import tempfile
|
||||
fd, out_path = tempfile.mkstemp(suffix='.json')
|
||||
os.close(fd)
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
[sys.executable, os.path.join(TOOLS, 'ba_pagespec.py'),
|
||||
'--out', out_path],
|
||||
cwd=ROOT, capture_output=True, text=True, timeout=120)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
with open(out_path, 'r', encoding='utf-8') as fh:
|
||||
spec = json.load(fh)
|
||||
assert spec['schema'] == 1
|
||||
assert spec['title'] == 'Beam Analysis'
|
||||
assert len(spec['pages']) == 5
|
||||
finally:
|
||||
try:
|
||||
os.remove(out_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def test_spec_geometry_entries_are_the_drawings_own_not_the_whole_map():
|
||||
"""Caught by the first headless screenshot: every geometry[key] held the
|
||||
entire map, so drawings read length_ft off a dict of dicts."""
|
||||
import json, os
|
||||
job = json.load(open(os.path.join(FIXTURES, "rba5_16x22.json")))
|
||||
spec = ba_pagespec.build_pagespec(reads=job)
|
||||
g = spec['geometry']
|
||||
assert 'extents.elevation' in g
|
||||
assert 'extents.elevation' not in g['extents.elevation'], 'nested map'
|
||||
assert g['extents.elevation']['length_ft'] > 17
|
||||
assert g['reinforcement.section']['bars']
|
||||
Reference in New Issue
Block a user