# Copyright 2014, Sandia Corporation. Under the terms of Contract
# DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains certain
# rights in this software.
from __future__ import absolute_import
import collections
import io
import json
import os
import numbers
import numpy.testing
import png
import re
import subprocess
import sys
import toyplot.html
import toyplot.svg
import xml.etree.ElementTree as xml
try:
import toyplot.pdf
except:
pass
try:
import toyplot.png
except:
pass
try:
import toyplot.reportlab.pdf
except:
pass
try:
import toyplot.reportlab.png
except:
pass
root_dir = os.path.dirname(os.path.dirname(__file__))
failed_dir = os.path.join(root_dir, "features", "failed")
reference_dir = os.path.join(root_dir, "features", "reference")
backend_dir = os.path.join(root_dir, "features", "backends")
def _assert_string_equal(content, test_file, reference_file, encoding="utf-8"):
if os.path.exists(test_file):
os.remove(test_file)
if os.path.exists(reference_file):
reference = toyplot.compatibility.unicode_type(
open(reference_file, "rb").read(), encoding=encoding)
if content != reference:
if not os.path.exists(failed_dir):
os.mkdir(failed_dir)
with open(test_file, "wb") as file:
file.write(content.encode(encoding))
raise AssertionError(
"Test output %s doesn't match %s." %
(test_file, reference_file))
else:
with open(reference_file, "wb") as file:
file.write(content.encode(encoding))
raise AssertionError(
"Created new reference file %s. You should verify its contents before re-running the test." %
(reference_file))
def _json_comparison_string(o):
"""Convert a Python object to a JSON string representation that can be used for comparison.
Limits the precision of floating-point numbers.
"""
if o is None:
return "null"
if isinstance(o, toyplot.compatibility.string_type):
return "\"" + o + "\""
if isinstance(o, numbers.Integral):
return str(o)
if isinstance(o, numbers.Real):
return "%.9g" % o
if isinstance(o, collections.Sequence):
return "[" + ",".join([_json_comparison_string(i) for i in o]) + "]"
if isinstance(o, collections.Mapping):
return "{" + ",".join(["\"" + key + "\":" + _json_comparison_string(value)
for key, value in o.items()]) + "}"
raise Exception("Unexpected value: %s" % o)
def _xml_comparison_string(element):
"""Convert an XML element to a pretty string representation that can be used for comparison.
Filters-out elements and attributes (like id) that shouldn't be compared,
and limits the precision of floating-point numbers.
"""
def format_value(value):
try:
return "%.9g" % float(value)
except:
return value
def write_element(element, buffer, indent):
buffer.write(u"%s<%s" % (indent, element.tag))
for key, value in element.items():
if key in ["id", "clip-path"]:
continue
if key == "style":
value = re.sub("fill:url[(]#.*[)]", "fill:url(#)", value)
if key == "d" and element.tag == "{http://www.w3.org/2000/svg}path":
buffer.write(
u" %s='%s'" % (key, " ".join([format_value(d) for d in value.split(" ")])))
elif key == "transform":
buffer.write(u" %s='%s'" % (
key, "".join([format_value(d) for d in re.split("(,|\(|\))", value)])))
elif key == "points" and element.tag == "{http://www.w3.org/2000/svg}polygon":
buffer.write(u" %s='%s'" % (key, " ".join(
[",".join([format_value(i) for i in p.split(",")]) for p in value.split(" ")])))
else:
buffer.write(u" %s='%s'" % (key, format_value(value)))
text = element.text if element.text is not None else ""
if element.tag in [
"{http://www.sandia.gov/toyplot}data-table",
"{http://www.sandia.gov/toyplot}coordinates"]:
text = str(_json_comparison_string(json.loads(element.text)))
buffer.write(u">%s\n" % text)
for child in list(element):
write_element(child, buffer, indent + " ")
buffer.write(u"%s</%s>\n" % (indent, element.tag))
buffer = io.StringIO()
write_element(element, buffer, indent="")
return buffer.getvalue()
[docs]def assert_color_equal(a, b):
"""Raise an exception if a toyplot color doesn't match a reference.
Parameters
----------
a: toyplot RGBA color, required
b: 4-tuple
"""
if a is None and b is None:
return
numpy.testing.assert_array_almost_equal(
(a["r"], a["g"], a["b"], a["a"]), b)
[docs]def assert_colors_equal(a, b):
"""Raise an exception if sequence of toyplot colors don't match a reference.
Parameters
----------
a: sequence of toyplot RGBA colors, required
b: sequence of 4-tuples
"""
for ai, bi in zip(a, b):
if ai is not None and bi is not None:
numpy.testing.assert_array_almost_equal(
(ai["r"], ai["g"], ai["b"], ai["a"]), bi)
[docs]def assert_html_equal(html, name):
"""Raise an exception if HTML content doesn't match a reference.
Parameters
----------
html: string, required
The HTML content to be compared.
name: string, required
Unique identifier of the reference file to use as a comparison.
The reference file will be located at toyplot/features/reference/<name>.html
"""
test_file = os.path.join(failed_dir, "%s.html" % name)
reference_file = os.path.join(reference_dir, "%s.html" % name)
_assert_string_equal(html, test_file, reference_file)
[docs]def assert_canvas_equal(canvas, name):
test_file = os.path.join(failed_dir, "%s.svg" % name)
reference_file = os.path.join(reference_dir, "%s.svg" % name)
# Render multiple representations of the canvas for coverage ...
html = io.BytesIO()
toyplot.html.render(canvas, html)
svg = io.BytesIO()
toyplot.svg.render(canvas, svg)
for module in ["toyplot.pdf", "toyplot.png", "toyplot.reportlab.pdf", "toyplot.reportlab.png"]:
if module in sys.modules:
buffer = io.BytesIO()
sys.modules[module].render(canvas, buffer)
# Get rid of any past failures ...
if os.path.exists(test_file):
os.remove(test_file)
# If there's no stored SVG reference for this canvas, create one ...
if not os.path.exists(reference_file):
with open(reference_file, "wb") as file:
file.write(svg.getvalue())
raise AssertionError(
"Created new reference file %s ... you should verify its contents before re-running the test." %
reference_file)
# Compare the SVG representation of the canvas to the SVG reference ...
svg_dom = xml.fromstring(svg.getvalue())
reference_dom = xml.parse(reference_file).getroot()
svg_string = _xml_comparison_string(svg_dom)
reference_string = _xml_comparison_string(reference_dom)
if svg_string != reference_string:
if not os.path.exists(failed_dir):
os.mkdir(failed_dir)
with open(test_file, "wb") as file:
file.write(svg.getvalue())
reference = subprocess.Popen(["xmldiff", test_file, reference_file], stdout=subprocess.PIPE)
test = subprocess.Popen(["xmldiff", reference_file, test_file], stdout=subprocess.PIPE)
message = "Test output %s doesn't match %s:\n\n*** Test -> Reference ***\n%s\n*** Reference -> Test ***\n%s\n" % (
test_file,
reference_file,
reference.communicate()[0],
test.communicate()[0],
)
raise AssertionError(message)
[docs]def read_png(fobj):
if isinstance(fobj, toyplot.compatibility.string_type):
reader = png.Reader(filename=fobj)
else:
reader = png.Reader(fobj)
width, height, pixels, meta = reader.read()
planes = 1 if meta["greyscale"] else 3
if meta["alpha"]:
planes += 1
if meta["bitdepth"] == 1:
image = numpy.resize(numpy.vstack(pixels), (height, width, planes))
elif meta["bitdepth"] == 8:
image = numpy.resize(numpy.vstack(map(numpy.uint8, pixels)), (height, width, planes))
return image