#!/usr/bin/env python3
################################################################################
#
#  CDDL HEADER START
#
#  The contents of this file are subject to the terms of the Common Development
#  and Distribution License Version 1.0 (the "License").
#
#  You can obtain a copy of the license at
#  http:# www.opensource.org/licenses/CDDL-1.0.  See the License for the
#  specific language governing permissions and limitations under the License.
#
#  When distributing Covered Code, include this CDDL HEADER in each file and
#  include the License file in a prominent location with the name LICENSE.CDDL.
#  If applicable, add the following below this CDDL HEADER, with the fields
#  enclosed by brackets "[]" replaced with your own identifying information:
#
#  Portions Copyright (c) [yyyy] [name of copyright owner]. All rights reserved.
#
#  CDDL HEADER END
#
#  Copyright (c) 2020, Regents of the University of Minnesota.
#  All rights reserved.
#
#  Contributor(s):
#     Ellad B. Tadmor
#     Daniel S. Karls
#
################################################################################

# The docstring below is vc_description
"""Check that the model has no memory leaks. This is tested using the Valgrind
memory debugging tool (http://valgrind.org) by performing a series of
energy and force calculations on a randomly distorted face-centered cubic (FCC)
cube base structure for both non-periodic and periodic boundary conditions.
Separate configurations are tested for each species supported by the model, as
well as one containing a random distribution of all species. Configurations
used for testing are provided as auxiliary files."""

# Python 2-3 compatible code issues
from __future__ import print_function

try:
    input = raw_input
except NameError:
    pass

import kim_python_utils.vc as kim_vc_utils
import runner2
import subprocess
import re

__version__ = "003"
__author__ = "Ellad Tadmor and Daniel S. Karls"

################################################################################
#
#   FUNCTIONS
#
################################################################################

################################################################################
def do_vc(model, vc):
    """
    Do memory leak check
    """
    # call the code used to look for memory leaks directly to generate
    # the output for the report
    runner2.memory_leaks_test_process(model, vc)

    # call memory leaks test process using valgrind to look for leaks
    try:
        valgrind_out = subprocess.check_output(
            [
                "valgrind",
                "--suppressions=./valgrind-python.supp",
                "python",
                "runner2.py",
                model,
            ],
            stderr=subprocess.STDOUT,
            universal_newlines=True,
        )
    except subprocess.CalledProcessError:
        raise RuntimeError("Failed to run valgrind; check that it is installed.")

    # print valgrind output to report
    dashwidth = 80
    vc.rwrite("")
    vc.rwrite("=" * dashwidth)
    vc.rwrite(" " * 7 + "VALGRIND OUTPUT")
    vc.rwrite("=" * dashwidth)
    vc.rwrite("")
    vc.rwrite(valgrind_out)
    vc.rwrite("=" * dashwidth)

    # determine grade
    vc.rwrite("")
    vc.rwrite(
        "To pass this verification check the number of bytes that are "
        '"definitely lost" '
    )
    vc.rwrite('or "indirectly lost" must be zero.')
    vc.rwrite("")
    vc.rwrite(
        "NOTE that Valgrind will typically report non-zero "
        '"possibly lost" bytes due to'
    )
    vc.rwrite(
        "Python's internal memory allocation and garbage collection "
        "that it does not monitor."
    )
    vc.rwrite("")
    definitely_lost = int(re.search("definitely lost: ([0-9]+)", valgrind_out).group(1))
    indirectly_lost = int(re.search("indirectly lost: ([0-9]+)", valgrind_out).group(1))
    leak_detected = definitely_lost > 0 or indirectly_lost > 0

    # Run again and generate full valgrind output in aux file
    try:
        valgrind_out = subprocess.check_output(
            [
                "valgrind",
                "--leak-check=full",
                "--show-leak-kinds=all",
                "python",
                "runner2.py",
                model,
            ],
            stderr=subprocess.STDOUT,
            universal_newlines=True,
        )
    except subprocess.CalledProcessError:
        raise RuntimeError("Failed to run valgrind; check that it is installed.")
    aux_file = "valgrind.out"
    vc.vc_files.append(aux_file)
    vc.write_aux_string(aux_file, valgrind_out)
    vc.rwrite('Full Valgrind output written to auxiliary file "{}"'.format(aux_file))
    if leak_detected:
        vc.rwrite(
            '(Search for the word "lost" in the file to identify ' "memory leaks.)"
        )
    vc.rwrite("")

    # report grade
    if not leak_detected:
        vc_grade = "P"
        vc_comment = "No memory leak detected."
    else:
        vc_grade = "F"
        vc_comment = "Memory leak detected."

    return vc_grade, vc_comment


################################################################################
#
#   MAIN PROGRAM
#
###############################################################################
if __name__ == "__main__":

    vcargs = {
        "vc_name": "vc-memory-leak",
        "vc_author": __author__,
        "vc_description": kim_vc_utils.vc_stripall(__doc__),
        "vc_category": "informational",
        "vc_grade_basis": "passfail",
        "vc_files": [],
        "vc_debug": False,  # Set to True to get exception traceback info
    }

    # Get the model extended KIM ID:
    model = input("Enter a model name:\n")

    # Execute VC
    kim_vc_utils.setup_and_run_vc(do_vc, model, **vcargs)