Files
willtools-demo/tools/tests/test_units_js.py
T
will 25592fa6c2
ci / test (push) Successful in 6m14s
WillTools 2026-09-09 -- clean snapshot of the development repo for review
History-free snapshot of the private development repository at 77a82eb.
Code, docs and the public catalogue only: no plant or job knowledge
layers, no client, job or producer names (tools/scrub.py clean against
the widened term list; a second word-level grep clean). The intro deck is
left out because its slide images cannot be checked for job drawings.
2026-09-09 08:35:15 -05:00

116 lines
4.3 KiB
Python

# -*- coding: utf-8 -*-
"""Cross-checks `tools/pa_web/static/units.js` against its CPython twin,
`panellift.units` -- the two are meant to be a byte-for-byte port of each
other (see units.py's own module docstring), and this is the one place that
proves it rather than trusting two hand-written files stayed in sync.
Node-free on purpose: this whole file SKIPS, never fails, when `node` is not
on PATH. A missing dev tool on some machine is not a reason a CPython test
run should go red, and the JS's own behaviour is otherwise unverified only
by inspection -- an acceptable gap, not a gate anyone has to work around.
"""
import json
import os
import shutil
import subprocess
import sys
import pytest
HERE = os.path.dirname(os.path.abspath(__file__))
TOOLS = os.path.abspath(os.path.join(HERE, os.pardir))
REPO = os.path.abspath(os.path.join(TOOLS, os.pardir))
UNITS_JS = os.path.join(TOOLS, 'pa_web', 'static', 'units.js')
PANELLIFT_SRC = os.path.join(REPO, 'panellift', 'src')
if PANELLIFT_SRC not in sys.path:
sys.path.insert(0, PANELLIFT_SRC)
from panellift import units # noqa: E402
NODE = shutil.which('node')
# (js function name, python function name, args, output kind)
# output kind 'str' compares exactly; 'num' compares within a float epsilon
# (JS and CPython float arithmetic can differ in the last bit).
CASES = [
('fmtFtIn', 'fmt_ft_in', [8.375], 'str'),
('fmtFtIn', 'fmt_ft_in', [None], 'str'),
('fmtFtIn', 'fmt_ft_in', [-0.5], 'str'),
('fmtFtIn', 'fmt_ft_in', [0.53125], 'str'), # 0'-6 3/8"
('fmtFtIn', 'fmt_ft_in', [37.583333333333336], 'str'), # carries to 37'-7"
('fmtFtIn', 'fmt_ft_in', [0.0], 'str'),
('fmtIn', 'fmt_in', [6.375], 'str'),
('fmtIn', 'fmt_in', [None], 'str'),
('fmtIn', 'fmt_in', [12.0], 'str'),
('fmtIn', 'fmt_in', [0.0], 'str'),
('parseLength', 'parse_length', ["8'-4 1/2\""], 'num'),
('parseLength', 'parse_length', ["8'4\""], 'num'),
('parseLength', 'parse_length', ["8' 4-1/2\""], 'num'),
('parseLength', 'parse_length', ['4 1/2"'], 'num'),
('parseLength', 'parse_length', ['100"'], 'num'),
('parseLength', 'parse_length', ["-0'-6\""], 'num'),
('parseLength', 'parse_length', ['5'], 'num'),
('parseLength', 'parse_length', ['5', 'in'], 'num'),
('parseLengthIn', 'parse_length_in', ['6 3/8"'], 'num'),
('parseLengthIn', 'parse_length_in', ['5', 'ft'], 'num'),
]
_HARNESS = """
'use strict';
var fs = require('fs');
global.window = global;
var src = fs.readFileSync(process.argv[2], 'utf8');
eval(src);
var Units = global.Units;
var cases = JSON.parse(fs.readFileSync(process.argv[3], 'utf8'));
var out = cases.map(function (c) {
try {
return {ok: true, value: Units[c.fn].apply(null, c.args)};
} catch (e) {
return {ok: false, error: String(e)};
}
});
process.stdout.write(JSON.stringify(out));
"""
def _run_js(tmp_path):
harness = tmp_path / 'harness.js'
harness.write_text(_HARNESS, encoding='utf-8')
cases_path = tmp_path / 'cases.json'
cases_path.write_text(
json.dumps([{'fn': fn, 'args': args} for fn, _p, args, _k in CASES]),
encoding='utf-8')
proc = subprocess.run(
[NODE, str(harness), UNITS_JS, str(cases_path)],
capture_output=True, text=True, timeout=30)
assert proc.returncode == 0, (
'node harness failed: stdout={0!r} stderr={1!r}'.format(
proc.stdout, proc.stderr))
return json.loads(proc.stdout)
@pytest.mark.skipif(NODE is None, reason='node is not on PATH')
def test_units_js_matches_units_py(tmp_path):
assert os.path.isfile(UNITS_JS), UNITS_JS
js_results = _run_js(tmp_path)
assert len(js_results) == len(CASES)
for (fn, py_name, args, kind), js in zip(CASES, js_results):
py_fn = getattr(units, py_name)
py_value = py_fn(*args)
assert js['ok'], '{0}{1} threw in JS: {2}'.format(fn, args,
js.get('error'))
js_value = js['value']
if kind == 'str':
assert js_value == py_value, (fn, args, js_value, py_value)
else:
assert abs(js_value - py_value) < 1e-9, (
fn, args, js_value, py_value)
def test_units_js_file_exists_even_without_node():
"""The one assertion that always runs: the file this whole module is
about is actually there, node or not."""
assert os.path.isfile(UNITS_JS)