2019-06-08 17:10:47 -04:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
|
|
#
|
|
|
|
# NopSCADlib Copyright Chris Palmer 2018
|
|
|
|
# nop.head@gmail.com
|
|
|
|
# hydraraptor.blogspot.com
|
|
|
|
#
|
|
|
|
# This file is part of NopSCADlib.
|
|
|
|
#
|
|
|
|
# NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
|
|
|
|
# GNU General Public License as published by the Free Software Foundation, either version 3 of
|
|
|
|
# the License, or (at your option) any later version.
|
|
|
|
#
|
|
|
|
# NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
|
|
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
# See the GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License along with NopSCADlib.
|
|
|
|
# If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
#
|
2019-06-11 10:33:13 -04:00
|
|
|
#! Runs all the tests in the tests directory and makes the readme file with a catalog of the results.
|
2019-06-08 17:10:47 -04:00
|
|
|
|
|
|
|
from __future__ import print_function
|
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
import openscad
|
|
|
|
import subprocess
|
|
|
|
import bom
|
|
|
|
import times
|
2020-02-22 14:44:01 -05:00
|
|
|
import options
|
2019-06-08 17:10:47 -04:00
|
|
|
import time
|
|
|
|
import json
|
2019-06-09 08:19:08 -04:00
|
|
|
import shutil
|
2019-06-08 17:10:47 -04:00
|
|
|
from deps import *
|
|
|
|
from blurb import *
|
2019-06-09 08:19:08 -04:00
|
|
|
from colorama import Fore
|
2019-06-08 17:10:47 -04:00
|
|
|
|
|
|
|
w = 4096
|
|
|
|
h = w
|
2019-06-10 05:00:47 -04:00
|
|
|
threshold = 20 # Image comparison allowed number of different pixels
|
2019-06-10 14:57:13 -04:00
|
|
|
fuzz = 5 # Image comparison allowed percentage error in pixel value
|
|
|
|
|
|
|
|
colour_scheme = "--colorscheme=Nature"
|
|
|
|
background = "#F8F8F8"
|
2019-06-08 17:10:47 -04:00
|
|
|
|
|
|
|
def do_cmd(cmd, output = sys.stdout):
|
|
|
|
for arg in cmd:
|
|
|
|
print(arg, end = " ")
|
|
|
|
print()
|
2019-06-09 08:19:08 -04:00
|
|
|
return subprocess.call(cmd, stdout = output, stderr = output)
|
|
|
|
|
2019-06-10 05:00:47 -04:00
|
|
|
def compare_images(a, b, c):
|
|
|
|
if not os.path.isfile(a):
|
|
|
|
return -1
|
|
|
|
log_name = 'magick.log'
|
|
|
|
with open(log_name, 'w') as output:
|
|
|
|
do_cmd(("magick compare -metric AE -fuzz %d%% %s %s %s" % (fuzz, a, b, c)).split(), output = output)
|
|
|
|
with open(log_name, 'r') as f:
|
2021-01-12 09:51:55 -05:00
|
|
|
pixels = int(float(f.read().strip()))
|
2019-06-10 05:00:47 -04:00
|
|
|
os.remove(log_name)
|
|
|
|
return pixels
|
|
|
|
|
2019-06-09 08:19:08 -04:00
|
|
|
def update_image(tmp_name, png_name):
|
|
|
|
"""Update an image only if different, otherwise just change the mod time"""
|
2019-06-10 05:00:47 -04:00
|
|
|
diff_name = png_name.replace('.png', '_diff.png')
|
|
|
|
pixels = compare_images(png_name, tmp_name, diff_name)
|
|
|
|
if pixels < 0 or pixels > threshold:
|
|
|
|
shutil.copyfile(tmp_name, png_name)
|
|
|
|
print(Fore.YELLOW + png_name + " updated" + Fore.WHITE, pixels if pixels > 0 else '')
|
|
|
|
else:
|
|
|
|
os.utime(png_name, None)
|
|
|
|
os.remove(diff_name)
|
|
|
|
os.remove(tmp_name)
|
2019-06-09 08:19:08 -04:00
|
|
|
|
2019-06-08 17:10:47 -04:00
|
|
|
|
|
|
|
def depluralise(name):
|
|
|
|
if name[-3:] == "ies" and name != "zipties":
|
|
|
|
return name[:-3] + 'y'
|
|
|
|
if name[-3:] == "hes":
|
|
|
|
return name[:-2]
|
|
|
|
if name[-1:] == 's':
|
|
|
|
return name[:-1]
|
|
|
|
return name
|
|
|
|
|
|
|
|
def is_plural(name):
|
|
|
|
return name != depluralise(name)
|
|
|
|
|
2020-03-11 19:09:03 -04:00
|
|
|
def usage():
|
|
|
|
print("\nusage:\n\ttests [test_name1] ... [test_nameN] - Run specified tests or all tests in none specified.");
|
|
|
|
sys.exit(1)
|
|
|
|
|
2019-06-08 17:10:47 -04:00
|
|
|
def tests(tests):
|
|
|
|
scad_dir = "tests"
|
|
|
|
deps_dir = scad_dir + "/deps"
|
|
|
|
png_dir = scad_dir + "/png"
|
|
|
|
bom_dir = scad_dir + "/bom"
|
|
|
|
for dir in [deps_dir, png_dir, bom_dir]:
|
|
|
|
if not os.path.isdir(dir):
|
|
|
|
os.makedirs(dir)
|
|
|
|
index = {}
|
|
|
|
bodies = {}
|
2020-03-11 19:09:03 -04:00
|
|
|
done = []
|
2019-06-08 17:10:47 -04:00
|
|
|
times.read_times()
|
2020-02-22 14:44:01 -05:00
|
|
|
options.check_options(deps_dir)
|
2019-06-08 17:10:47 -04:00
|
|
|
#
|
|
|
|
# Make cover pic if does not exist as very slow. Delete it to force an update.
|
|
|
|
#
|
|
|
|
png_name = "libtest.png"
|
|
|
|
scad_name = "libtest.scad"
|
2020-11-09 11:17:02 -05:00
|
|
|
if os.path.isfile(scad_name):
|
|
|
|
libtest = True
|
|
|
|
lib_blurb = scrape_blurb(scad_name)
|
|
|
|
if not os.path.isfile(png_name):
|
|
|
|
openscad.run(colour_scheme, "--projection=p", "--imgsize=%d,%d" % (w, h), "--camera=0,0,0,50,0,340,500", "--autocenter", "--viewall", "-o", png_name, scad_name);
|
|
|
|
do_cmd(["magick", png_name, "-trim", "-resize", "1280", "-bordercolor", background, "-border", "10", png_name])
|
|
|
|
else:
|
|
|
|
#
|
|
|
|
# Project tests so just a title
|
|
|
|
#
|
|
|
|
libtest = False
|
|
|
|
project = ' '.join(word[0].upper() + word[1:] for word in os.path.basename(os.getcwd()).split('_'))
|
|
|
|
lib_blurb = '#' + project + ' Tests\n'
|
|
|
|
|
|
|
|
doc_base_name = "readme" if libtest else "tests"
|
|
|
|
doc_name = doc_base_name + ".md"
|
2019-06-08 17:10:47 -04:00
|
|
|
#
|
|
|
|
# List of individual part files
|
|
|
|
#
|
|
|
|
|
2020-11-09 11:17:02 -05:00
|
|
|
scads = [i for i in sorted(os.listdir(scad_dir), key = lambda s: s.lower()) if i[-5:] == ".scad"]
|
|
|
|
types = []
|
2019-06-08 17:10:47 -04:00
|
|
|
for scad in scads:
|
|
|
|
base_name = scad[:-5]
|
|
|
|
if not tests or base_name in tests:
|
2020-03-11 19:09:03 -04:00
|
|
|
done.append(base_name)
|
2020-11-08 09:56:52 -05:00
|
|
|
print('\n'+base_name)
|
2019-06-08 17:10:47 -04:00
|
|
|
cap_name = base_name[0].capitalize() + base_name[1:]
|
2020-03-03 15:33:53 -05:00
|
|
|
base_name = base_name.lower()
|
2019-06-08 17:10:47 -04:00
|
|
|
scad_name = scad_dir + '/' + scad
|
|
|
|
png_name = png_dir + '/' + base_name + '.png'
|
|
|
|
bom_name = bom_dir + '/' + base_name + '.json'
|
|
|
|
|
|
|
|
objects_name = None
|
|
|
|
vits_name = 'vitamins/' + base_name + '.scad'
|
|
|
|
if is_plural(base_name) and os.path.isfile(vits_name):
|
|
|
|
objects_name = vits_name
|
|
|
|
|
2020-11-09 11:17:02 -05:00
|
|
|
locations = []
|
|
|
|
if os.path.isdir('vitamins'):
|
|
|
|
locations.append(('vitamins/' + depluralise(base_name) + '.scad', 'Vitamins'))
|
|
|
|
if os.path.isdir('printed'):
|
|
|
|
locations.append(('printed/' + base_name + '.scad', 'Printed'))
|
|
|
|
if os.path.isdir('utils'):
|
|
|
|
locations.append(('utils/' + base_name + '.scad', 'Utilities'))
|
|
|
|
if libtest and os.path.isdir('utils/core'):
|
|
|
|
locations.append(('utils/core/' + base_name + '.scad', 'Core Utilities'))
|
2019-06-08 17:10:47 -04:00
|
|
|
|
|
|
|
for name, type in locations:
|
|
|
|
if os.path.isfile(name):
|
|
|
|
impl_name = name
|
|
|
|
break
|
|
|
|
else:
|
2020-11-10 07:01:57 -05:00
|
|
|
if libtest:
|
|
|
|
print("Can't find implementation!")
|
|
|
|
continue
|
|
|
|
else:
|
|
|
|
type = 'Tests' # OK when testing part of a project
|
|
|
|
impl_name = None
|
2019-06-08 17:10:47 -04:00
|
|
|
|
2020-11-09 11:17:02 -05:00
|
|
|
if libtest:
|
|
|
|
vsplit = "AJR" + chr(ord('Z') + 1)
|
|
|
|
vtype = locations[0][1]
|
|
|
|
types = [vtype + ' ' + vsplit[i] + '-' + chr(ord(vsplit[i + 1]) - 1) for i in range(len(vsplit) - 1)] + [loc[1] for loc in locations[1 :]]
|
|
|
|
if type == vtype:
|
|
|
|
for i in range(1, len(vsplit)):
|
|
|
|
if cap_name[0] < vsplit[i]:
|
|
|
|
type = types[i - 1]
|
|
|
|
break
|
|
|
|
else:
|
2020-11-10 07:01:57 -05:00
|
|
|
if not types:
|
|
|
|
types = [loc[1] for loc in locations] # No need to split up the vitamin list
|
|
|
|
if not type in types: # Will happen when implementation is not found and type is set to Tests
|
|
|
|
types.append(type)
|
2019-06-08 17:10:47 -04:00
|
|
|
|
2019-06-13 14:47:02 -04:00
|
|
|
for t in types:
|
|
|
|
if not t in bodies:
|
|
|
|
bodies[t] = []
|
|
|
|
index[t] = []
|
|
|
|
|
2019-06-08 17:10:47 -04:00
|
|
|
body = bodies[type]
|
|
|
|
|
|
|
|
index[type] += [cap_name]
|
|
|
|
body += ['<a name="%s"></a>' % cap_name]
|
|
|
|
body += ["## " + cap_name]
|
|
|
|
|
|
|
|
doc = None
|
|
|
|
if impl_name:
|
|
|
|
doc = scrape_code(impl_name)
|
|
|
|
blurb = doc["blurb"]
|
|
|
|
else:
|
|
|
|
blurb = scrape_blurb(scad_name)
|
|
|
|
|
|
|
|
if not len(blurb):
|
|
|
|
print("Blurb not found!")
|
|
|
|
else:
|
|
|
|
body += [ blurb ]
|
|
|
|
|
|
|
|
if objects_name:
|
|
|
|
body += ["[%s](%s) Object definitions.\n" % (objects_name, objects_name)]
|
|
|
|
|
|
|
|
if impl_name:
|
|
|
|
body += ["[%s](%s) Implementation.\n" % (impl_name, impl_name)]
|
|
|
|
|
|
|
|
body += ["[%s](%s) Code for this example.\n" % (scad_name.replace('\\','/'), scad_name)]
|
|
|
|
|
|
|
|
if doc:
|
2019-06-09 03:11:00 -04:00
|
|
|
for thing, heading in [("properties", "Function"), ("functions", "Function"), ("modules", "Module")]:
|
2019-06-08 17:10:47 -04:00
|
|
|
things = doc[thing]
|
|
|
|
if things:
|
2019-06-09 03:11:00 -04:00
|
|
|
body += ['### %s\n| %s | Description |\n|:--- |:--- |' % (thing.title(), heading)]
|
2019-06-08 17:10:47 -04:00
|
|
|
for item in sorted(things):
|
2020-12-24 11:04:59 -05:00
|
|
|
body += ['| `%s` | %s |' % (item, things[item])]
|
2019-06-08 17:10:47 -04:00
|
|
|
body += ['']
|
|
|
|
|
|
|
|
body += ["![%s](%s)\n" %(base_name, png_name)]
|
|
|
|
|
2020-03-03 15:33:53 -05:00
|
|
|
dname = deps_name(deps_dir, scad.lower())
|
2019-08-18 05:53:22 -04:00
|
|
|
oldest = png_name if mtime(png_name) < mtime(bom_name) else bom_name
|
2019-06-08 17:10:47 -04:00
|
|
|
changed = check_deps(oldest, dname)
|
2020-04-13 13:02:03 -04:00
|
|
|
changed = times.check_have_time(changed, scad_name)
|
2020-02-22 14:44:01 -05:00
|
|
|
changed = options.have_changed(changed, oldest)
|
2019-06-08 17:10:47 -04:00
|
|
|
if changed:
|
|
|
|
print(changed)
|
|
|
|
t = time.time()
|
2019-06-09 08:19:08 -04:00
|
|
|
tmp_name = 'tmp.png'
|
2020-02-22 14:44:01 -05:00
|
|
|
openscad.run_list(options.list() + ["-D$bom=2", colour_scheme, "--projection=p", "--imgsize=%d,%d" % (w, h), "--camera=0,0,0,70,0,315,500", "--autocenter", "--viewall", "-d", dname, "-o", tmp_name, scad_name]);
|
2019-06-08 17:10:47 -04:00
|
|
|
times.add_time(scad_name, t)
|
2019-06-10 14:57:13 -04:00
|
|
|
do_cmd(["magick", tmp_name, "-trim", "-resize", "1000x600", "-bordercolor", background, "-border", "10", tmp_name])
|
2019-06-09 08:19:08 -04:00
|
|
|
update_image(tmp_name, png_name)
|
2019-06-08 17:10:47 -04:00
|
|
|
BOM = bom.parse_bom()
|
|
|
|
with open(bom_name, 'wt') as outfile:
|
|
|
|
json.dump(BOM.flat_data(), outfile, indent = 4)
|
|
|
|
|
|
|
|
with open(bom_name, "rt") as bom_file:
|
|
|
|
BOM = json.load(bom_file)
|
2019-06-09 03:11:00 -04:00
|
|
|
for thing, heading in [("vitamins", "Module call | BOM entry") , ("printed", "Filename"), ("routed", "Filename"), ("assemblies", "Name")]:
|
2019-06-08 17:10:47 -04:00
|
|
|
things = BOM[thing]
|
|
|
|
if things:
|
2019-06-09 03:11:00 -04:00
|
|
|
body += ['### %s\n| Qty | %s |\n| ---:|:--- |%s' % (thing.title(), heading, ':---|' if '|' in heading else '')]
|
2019-06-08 17:10:47 -04:00
|
|
|
for item in sorted(things, key = lambda s: s.split(":")[-1]):
|
|
|
|
name = item
|
|
|
|
desc = ''
|
|
|
|
if thing == "vitamins":
|
|
|
|
vit = item.split(':')
|
2020-12-24 11:04:59 -05:00
|
|
|
name = '`' + vit[0] + '`' if vit[0] else ''
|
2019-06-08 17:10:47 -04:00
|
|
|
while '[[' in name and ']]' in name:
|
|
|
|
i = name.find('[[')
|
|
|
|
j = name.find(']]') + 2
|
|
|
|
name = name.replace(name[i : j], '[ ... ]')
|
|
|
|
desc = vit[1]
|
2020-04-05 11:18:24 -04:00
|
|
|
body += ['| %3d | %s | %s |' % (things[item]["count"], name, desc)]
|
2019-06-09 03:11:00 -04:00
|
|
|
else:
|
2020-04-05 11:18:24 -04:00
|
|
|
count = things[item] if thing == 'assemblies' else things[item]["count"]
|
|
|
|
body += ['| %3d | %s |' % (count, name)]
|
2019-06-08 17:10:47 -04:00
|
|
|
body += ['']
|
|
|
|
|
|
|
|
body += ['\n<a href="#top">Top</a>']
|
|
|
|
body += ["\n---"]
|
|
|
|
|
2020-03-11 19:09:03 -04:00
|
|
|
for test in done:
|
|
|
|
if test in tests:
|
|
|
|
tests.remove(test)
|
|
|
|
if tests:
|
|
|
|
for test in tests:
|
|
|
|
print(Fore.MAGENTA + "Could not find a test called", test, Fore.WHITE)
|
|
|
|
usage()
|
|
|
|
|
2019-06-08 17:10:47 -04:00
|
|
|
with open(doc_name, "wt") as doc_file:
|
2020-11-09 11:17:02 -05:00
|
|
|
print(lib_blurb, file = doc_file)
|
2019-06-08 17:10:47 -04:00
|
|
|
print('## Table of Contents<a name="top"/>', file = doc_file)
|
|
|
|
print('<table><tr>', file = doc_file)
|
|
|
|
n = 0
|
|
|
|
for type in types:
|
|
|
|
print('<th align="left"> %s </th>' % type, end = '', file = doc_file)
|
|
|
|
n = max(n, len(index[type]))
|
|
|
|
print('</tr>', file = doc_file)
|
|
|
|
for i in range(n):
|
|
|
|
print('<tr>', file = doc_file, end = '')
|
|
|
|
for type in types:
|
|
|
|
if i < len(index[type]):
|
2019-08-18 09:26:14 -04:00
|
|
|
name = sorted(index[type])[i]
|
2019-06-08 17:10:47 -04:00
|
|
|
print('<td> <a href = "#' + name + '">' + name + '</a> </td>', file = doc_file, end = '')
|
|
|
|
else:
|
|
|
|
print('<td></td>', file = doc_file, end = '')
|
|
|
|
print('</tr>', file = doc_file)
|
|
|
|
print('</table>\n\n---', file = doc_file)
|
|
|
|
for type in types:
|
|
|
|
for line in bodies[type]:
|
|
|
|
print(line, file = doc_file)
|
2020-11-08 09:56:52 -05:00
|
|
|
with open(doc_base_name + ".html", "wt") as html_file:
|
|
|
|
do_cmd(("python -m markdown -x tables " + doc_name).split(), html_file)
|
2019-06-08 17:10:47 -04:00
|
|
|
times.print_times()
|
2020-11-08 09:56:52 -05:00
|
|
|
do_cmd(('codespell -L od ' + doc_name).split())
|
2019-06-08 17:10:47 -04:00
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2020-03-11 19:09:03 -04:00
|
|
|
for arg in sys.argv[1:]:
|
|
|
|
if arg[:1] == '-': usage()
|
2019-06-08 17:10:47 -04:00
|
|
|
tests(sys.argv[1:])
|