Source code for schrodinger.test.perceptualdiff

"""
Run Perceptual Diff tests in maestrolibs
"""

import email.utils
import enum
import getpass
import glob
import os
import re
import smtplib
import subprocess
from email.mime import image
from email.mime import multipart
from email.mime import text
from subprocess import CalledProcessError
from subprocess import check_output

import schrodinger.test.pytest.exetest

MAIL_HOST = 'nyc-mail.schrodinger.com'

MAESTRO_USERS = {
    'bertel',
    'butsev',
    'devendra',
    'goyal',
    'jain',
    'javvaji',
    'scottm',
    'ssingh',
}


[docs]@enum.unique class Notify(enum.Enum): No = "no" Email = "email" Terminal = "terminal"
[docs]def get_notification_method(): """ Get the failure notification method based on username and the SCHRODINGER_IMAGE_DIFF_NOTIFICATION environment variable. """ if "SCHRODINGER_IMAGE_DIFF_NOTIFICATION" in os.environ: notification = os.environ["SCHRODINGER_IMAGE_DIFF_NOTIFICATION"] try: return Notify(notification) except ValueError: raise ValueError("SCHRODINGER_IMAGE_DIFF_NOTIFICATION needs to " "be set to 'no', 'email', or 'terminal'.") current_user = getpass.getuser() if current_user == 'buildbot': return Notify.Email elif current_user in MAESTRO_USERS: return Notify.Terminal else: return Notify.No
def _get_mesa_dir(): """ If an old SGL is being used, we should use the SGL from /software/lib that matches the build version. """ version = os.environ.get("MESA_VERSION") if version: return os.path.join(os.environ["SCHRODINGER_LIB"], os.environ["OS_CPU"], version, 'lib') import glob sgl = os.path.join(os.environ["SCHRODINGER_LIB"], os.environ["OS_CPU"], 'Mesa-*', 'lib') return sorted(glob.glob(sgl))[-1]
[docs]def get_opengl_version_string(): if not hasattr(get_opengl_version_string, "cache"): try: output = check_output(['glxinfo'], stderr=subprocess.DEVNULL, text=True) opengl_version = re.findall('OpenGL version string: (.*)', output)[0] get_opengl_version_string.cache = opengl_version except (OSError, CalledProcessError, IndexError): # Command doesn't exist or OpenGL not in use. get_opengl_version_string.cache = '' return get_opengl_version_string.cache
[docs]class PerceptualDiffTest(schrodinger.test.pytest.exetest.ExecutableTest): """ The tests that use perceptualdiff all require some boilerplate arguments: -r: path to the result image -v: path to the valid images (without _<number>.png) -d: path to the difference image """
[docs] def __init__(self, *, name=None, parent=None, valid_path=None, valid_filename=None): super().__init__(name=name, parent=parent) self.valid_path = valid_path self.valid_filename = valid_filename
[docs] def getCommand(self): valid_filename = self.valid_filename or 'valid_' + self.name valid_path = os.path.join(self.valid_path, valid_filename) cmd = super().getCommand() cmd.extend(('-r', f'result_{self.name}.png', '-v', valid_path, '-d', f'diff_{self.name}.png')) return cmd
[docs] def runtest(self, env=None): try: return super().runtest(env) except Exception as e: notification = get_notification_method() if notification is Notify.Terminal: raise elif notification is Notify.Email: email_failure_summary(self.name, self.valid_path, e) elif notification is not Notify.No: raise
[docs]def email_failure_summary(name, valid_path, exc): """ Email a summary of the perceptualdiff failure. """ from_addr = f'{getpass.getuser()}@schrodinger.com' to_addrs = ['imagediff-group@schrodinger.com'] msg = multipart.MIMEMultipart() msg['Date'] = email.utils.formatdate() msg['From'] = from_addr msg['To'] = ", ".join(to_addrs) msg['Subject'] = f"Broken perceptualdiff test for {name}" msg.attach( text.MIMEText("The perceptualdiff test for " "{} failed. Please see attached files.\n\n" "Git:\n{}py.test exception:\n{}".format( name, git_summary(), exc))) files = [f'result_{name}.png', f'diff_{name}.png'] valid_files = glob.glob( os.path.join(valid_path, "valid_") + name + "_*.png") files.extend(valid_files) for filename in files: with open(filename, 'rb') as f: name = os.path.basename(filename) attachment = image.MIMEImage(f.read(), Name=name) msg.attach(attachment) mailer = smtplib.SMTP(MAIL_HOST) mailer.sendmail(from_addr, to_addrs, msg.as_string()) mailer.quit()
[docs]def git_summary(): """ Return a string summarizing the state of the mmshare git repo. """ if 'SCHRODINGER_SRC' not in os.environ: return "No SCHRODINGER_SRC is available, so git repo info is nil." mmshare_dir = os.path.join(os.environ['SCHRODINGER_SRC'], "mmshare") p = subprocess.Popen(["git", "rev-parse", "HEAD"], cwd=mmshare_dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) rev_parse_out, err = p.communicate() if p.returncode != 0: rev_parse_out = f"failed with stderr of '{err}'" p = subprocess.Popen(["git", "status"], cwd=mmshare_dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) status_out, err = p.communicate() if p.returncode != 0: status_out = f"failed with stderr of '{err}'" return "git rev-parse HEAD\n{}\n\ngit status\n{}\n\n".format( rev_parse_out, status_out)