diff --git a/src/DIRAC/Core/Utilities/Graphs/Graph.py b/src/DIRAC/Core/Utilities/Graphs/Graph.py index 2162dd8d968..faf564222ed 100644 --- a/src/DIRAC/Core/Utilities/Graphs/Graph.py +++ b/src/DIRAC/Core/Utilities/Graphs/Graph.py @@ -6,6 +6,7 @@ """ import datetime +import importlib import time import os @@ -274,18 +275,13 @@ def makeGraph(self, data, *args, **kw): for i in range(nPlots): plot_type = plot_prefs[i]["plot_type"] try: - # TODO: Remove when we moved to python3 - exec(f"import {plot_type}") - except ImportError: - print("Trying to use python like import") - try: - exec(f"from . import {plot_type}") - except ImportError as x: - print(f"Failed to import graph type {plot_type}: {str(x)}") - return None + plotModule = importlib.import_module(f"DIRAC.Core.Utilities.Graphs.{plot_type}") + except ModuleNotFoundError as x: + print(f"Failed to import graph type {plot_type}: {str(x)}") + return None ax = plot_axes[i] - plot = eval(f"{plot_type}.{plot_type}(graphData[i],ax,plot_prefs[i])") + plot = getattr(plotModule, plot_type)(graphData[i], ax, plot_prefs[i]) plot.draw() if DEBUG: diff --git a/src/DIRAC/tests/Utilities/plots.py b/src/DIRAC/tests/Utilities/plots.py index 52001f76b41..fbc6f3d5afd 100644 --- a/src/DIRAC/tests/Utilities/plots.py +++ b/src/DIRAC/tests/Utilities/plots.py @@ -1,6 +1,8 @@ # pylint: disable=protected-access +import glob import math import operator +import os from functools import reduce from PIL import Image @@ -24,3 +26,37 @@ def compare(file1Path, file2Path): h2 = image2.histogram() rms = math.sqrt(reduce(operator.add, map(lambda a, b: (a - b) ** 2, h1, h2)) / len(h1)) return rms + + +def referenceImages(directory, stem): + """Return every reference image available for a given plot. + + The primary reference is ``.png``. Additional references may be added + as ``..png`` (for example ``histogram1.mpl-3.10.png``) when a new + matplotlib version renders a plot slightly differently. Keeping several + references side by side means a plot is accepted against any supported + plotting-stack version, so an upgrade only needs an extra reference image + rather than replacing the existing one (which would break older versions). + + :param str directory: Directory holding the reference images. + :param str stem: Base name of the plot, without extension. + :return: Sorted list of reference image paths. + """ + paths = glob.glob(os.path.join(directory, f"{stem}.png")) + paths += glob.glob(os.path.join(directory, f"{stem}.*.png")) + return sorted(paths) + + +def compareToReferences(generatedPath, referencePaths): + """Compare a generated plot against several candidate reference images. + + :param str generatedPath: Path to the freshly generated plot. + :param referencePaths: Iterable of reference image paths to compare against. + + :return: The smallest RMS obtained against any of the references, so a plot + is accepted as soon as it is identical (rms == 0.0) to one of them. + """ + referencePaths = list(referencePaths) + if not referencePaths: + raise ValueError(f"No reference images provided for {generatedPath}") + return min(compare(generatedPath, reference) for reference in referencePaths) diff --git a/tests/Integration/AccountingSystem/Test_Plots.py b/tests/Integration/AccountingSystem/Test_Plots.py index 2d3829b8604..347bb170b88 100644 --- a/tests/Integration/AccountingSystem/Test_Plots.py +++ b/tests/Integration/AccountingSystem/Test_Plots.py @@ -20,7 +20,7 @@ generateErrorMessagePlot, ) -from DIRAC.tests.Utilities.plots import compare +from DIRAC.tests.Utilities.plots import compareToReferences, referenceImages plots_directory = os.path.join(os.path.dirname(__file__), "plots") filename = "plot.png" @@ -34,7 +34,7 @@ def test_histogram(): res = generateHistogram(filename, [2, 2, 3, 4, 5, 5], {}) assert res["OK"] is True - res = compare(filename, os.path.join(plots_directory, "histogram1.png")) + res = compareToReferences(filename, referenceImages(plots_directory, "histogram1")) assert res == 0.0 res = generateHistogram( @@ -42,13 +42,13 @@ def test_histogram(): ) assert res["OK"] is True - res = compare(filename, os.path.join(plots_directory, "histogram2.png")) + res = compareToReferences(filename, referenceImages(plots_directory, "histogram2")) assert res == 0.0 res = generateHistogram(filename, [{"a": [1]}, {"b": [2, 3, 3, 5, 5]}], {}) assert res["OK"] is True - res = compare(filename, os.path.join(plots_directory, "histogram3.png")) + res = compareToReferences(filename, referenceImages(plots_directory, "histogram3")) assert res == 0.0 @@ -104,7 +104,7 @@ def test_stackedlineplots(): assert res["OK"] is True - res = compare(filename, os.path.join(plots_directory, "stackedline.png")) + res = compareToReferences(filename, referenceImages(plots_directory, "stackedline")) assert res == 0.0 @@ -115,7 +115,7 @@ def test_piechartplot(): res = generatePiePlot(filename, {"a": 16.0, "b": 56.0, "c": 15, "d": 20}, {}) assert res["OK"] is True - res = compare(filename, os.path.join(plots_directory, "piechart.png")) + res = compareToReferences(filename, referenceImages(plots_directory, "piechart")) assert res == 0.0 @@ -167,7 +167,7 @@ def test_cumulativeplot(): assert res["OK"] is True - res = compare(filename, os.path.join(plots_directory, "cumulativeplot.png")) + res = compareToReferences(filename, referenceImages(plots_directory, "cumulativeplot")) assert res == 0.0 @@ -183,7 +183,7 @@ def test_qualityplot(): ) assert res["OK"] is True - res = compare(filename, os.path.join(plots_directory, "qualityplot1.png")) + res = compareToReferences(filename, referenceImages(plots_directory, "qualityplot1")) assert res == 0.0 res = generateQualityPlot( @@ -193,7 +193,7 @@ def test_qualityplot(): ) assert res["OK"] is True - res = compare(filename, os.path.join(plots_directory, "qualityplot2.png")) + res = compareToReferences(filename, referenceImages(plots_directory, "qualityplot2")) assert res == 0.0 @@ -269,7 +269,7 @@ def test_timestackedbarplot(): ) assert res["OK"] is True - res = compare(filename, os.path.join(plots_directory, "timedstackedbarplot.png")) + res = compareToReferences(filename, referenceImages(plots_directory, "timedstackedbarplot")) assert res == 0.0 @@ -280,7 +280,7 @@ def test_nodataplot(): res = generateNoDataPlot(filename, {}, {"title": "Test plot"}) assert res["OK"] is True - res = compare(filename, os.path.join(plots_directory, "nodata.png")) + res = compareToReferences(filename, referenceImages(plots_directory, "nodata")) assert res == 0.0 @@ -293,5 +293,5 @@ def test_error(): with open(filename, "wb") as out: out.write(res) - res = compare(filename, os.path.join(plots_directory, "error.png")) + res = compareToReferences(filename, referenceImages(plots_directory, "error")) assert res == 0.0 diff --git a/tests/Integration/AccountingSystem/plots/cumulativeplot.mpl-3.11.0.png b/tests/Integration/AccountingSystem/plots/cumulativeplot.mpl-3.11.0.png new file mode 100644 index 00000000000..e2b7c5e4b58 Binary files /dev/null and b/tests/Integration/AccountingSystem/plots/cumulativeplot.mpl-3.11.0.png differ diff --git a/tests/Integration/AccountingSystem/plots/error.mpl-3.11.0.png b/tests/Integration/AccountingSystem/plots/error.mpl-3.11.0.png new file mode 100644 index 00000000000..be5ef1bcc80 Binary files /dev/null and b/tests/Integration/AccountingSystem/plots/error.mpl-3.11.0.png differ diff --git a/tests/Integration/AccountingSystem/plots/histogram1.mpl-3.11.0.png b/tests/Integration/AccountingSystem/plots/histogram1.mpl-3.11.0.png new file mode 100644 index 00000000000..ab416483dcf Binary files /dev/null and b/tests/Integration/AccountingSystem/plots/histogram1.mpl-3.11.0.png differ diff --git a/tests/Integration/AccountingSystem/plots/histogram2.mpl-3.11.0.png b/tests/Integration/AccountingSystem/plots/histogram2.mpl-3.11.0.png new file mode 100644 index 00000000000..3602194e2e9 Binary files /dev/null and b/tests/Integration/AccountingSystem/plots/histogram2.mpl-3.11.0.png differ diff --git a/tests/Integration/AccountingSystem/plots/histogram3.mpl-3.11.0.png b/tests/Integration/AccountingSystem/plots/histogram3.mpl-3.11.0.png new file mode 100644 index 00000000000..cc92de65ad7 Binary files /dev/null and b/tests/Integration/AccountingSystem/plots/histogram3.mpl-3.11.0.png differ diff --git a/tests/Integration/AccountingSystem/plots/nodata.mpl-3.11.0.png b/tests/Integration/AccountingSystem/plots/nodata.mpl-3.11.0.png new file mode 100644 index 00000000000..461f1f4bc71 Binary files /dev/null and b/tests/Integration/AccountingSystem/plots/nodata.mpl-3.11.0.png differ diff --git a/tests/Integration/AccountingSystem/plots/piechart.mpl-3.11.0.png b/tests/Integration/AccountingSystem/plots/piechart.mpl-3.11.0.png new file mode 100644 index 00000000000..d746467866c Binary files /dev/null and b/tests/Integration/AccountingSystem/plots/piechart.mpl-3.11.0.png differ diff --git a/tests/Integration/AccountingSystem/plots/qualityplot1.mpl-3.11.0.png b/tests/Integration/AccountingSystem/plots/qualityplot1.mpl-3.11.0.png new file mode 100644 index 00000000000..15e008d9763 Binary files /dev/null and b/tests/Integration/AccountingSystem/plots/qualityplot1.mpl-3.11.0.png differ diff --git a/tests/Integration/AccountingSystem/plots/qualityplot2.mpl-3.11.0.png b/tests/Integration/AccountingSystem/plots/qualityplot2.mpl-3.11.0.png new file mode 100644 index 00000000000..3d4d799a4a2 Binary files /dev/null and b/tests/Integration/AccountingSystem/plots/qualityplot2.mpl-3.11.0.png differ diff --git a/tests/Integration/AccountingSystem/plots/stackedline.mpl-3.11.0.png b/tests/Integration/AccountingSystem/plots/stackedline.mpl-3.11.0.png new file mode 100644 index 00000000000..e1cdd8518f7 Binary files /dev/null and b/tests/Integration/AccountingSystem/plots/stackedline.mpl-3.11.0.png differ diff --git a/tests/Integration/AccountingSystem/plots/timedstackedbarplot.mpl-3.11.0.png b/tests/Integration/AccountingSystem/plots/timedstackedbarplot.mpl-3.11.0.png new file mode 100644 index 00000000000..5d6549ebf12 Binary files /dev/null and b/tests/Integration/AccountingSystem/plots/timedstackedbarplot.mpl-3.11.0.png differ diff --git a/tests/Integration/DataManagementSystem/Test_UserMetadata.py b/tests/Integration/DataManagementSystem/Test_UserMetadata.py index fffd0c20d4a..b5b00e4c743 100644 --- a/tests/Integration/DataManagementSystem/Test_UserMetadata.py +++ b/tests/Integration/DataManagementSystem/Test_UserMetadata.py @@ -122,8 +122,8 @@ def test_metaIndex(self): # meta show result = self.fc.getMetadataFields() self.assertTrue(result["OK"]) - self.assertDictContainsSubset({"MetaInt6": "INT"}, result["Value"]["FileMetaFields"]) - self.assertDictContainsSubset({"TestDirectory6": "INT"}, result["Value"]["DirectoryMetaFields"]) + self.assertLessEqual({"MetaInt6": "INT"}.items(), result["Value"]["FileMetaFields"].items()) + self.assertLessEqual({"TestDirectory6": "INT"}.items(), result["Value"]["DirectoryMetaFields"].items()) # meta set metaDict6 = {"MetaInt6": 13} @@ -147,7 +147,7 @@ def test_metaIndex(self): # API call only result = self.fc.getFileUserMetadata(self.lfn5) self.assertTrue(result["OK"]) - self.assertDictContainsSubset({"MetaInt6": 13}, result["Value"]) + self.assertLessEqual({"MetaInt6": 13}.items(), result["Value"].items()) # file: expect a failure result = self.fc.getDirectoryUserMetadata(self.lfn5) self.assertFalse(result["OK"]) @@ -155,7 +155,7 @@ def test_metaIndex(self): # directory result = self.fc.getDirectoryUserMetadata(self.dir5) self.assertTrue(result["OK"]) - self.assertDictContainsSubset({"TestDirectory6": 126}, result["Value"]) + self.assertLessEqual({"TestDirectory6": 126}.items(), result["Value"].items()) # finally remove # meta remove lfn5 MetaInt6