1#!/usr/bin/env python
2from __future__ import absolute_import, print_function, unicode_literals
3from subprocess import call, check_output
4import os       # For filesystem access
5import sys      # For sys.exit()
6import argparse # For parsing command-line arguments
7import urllib   # For downloading the ssget index
8import ssl
9import tarfile  # For un-tar/unzipping matrix files
10import csv      # For reading the ssget index
11import shutil   # For using 'which'
12from distutils.spawn import find_executable # For using find_executable
13
14def main():
15    # Check to make sure we're in the build directory - if not, exit
16    checkLocation()
17
18    # Parse the command-line arguments
19    args = parseArguments()
20
21    # Run tests if needed
22    if args.tests != "none":
23        runTests(args)
24
25    # If the coverage flag is on, run gcov (or similar)
26    if args.coverage or args.html_coverage:
27        runCoverageUtility(args)
28
29def runTests(args):
30    # Create or locate matrix temporary storage directory
31    matrix_dir = getMatrixDirectory(args)
32
33    # Download the matrix stats csv file
34    stats_file = downloadStatsFile(matrix_dir)
35
36    with open(stats_file, 'rb') as f:
37        reader = csv.reader(f)
38
39        # Matrix IDs are not listed in the stats file - we just have to keep count
40        matrix_id = 0
41        for row in reader:
42
43            if len(row) == 13: # Only rows with 13 elements represent matrix data
44                matrix_id += 1
45
46                # Check if the matrix ID is in the proper range and
47                # that the matrix is real and symmetric
48                isInBounds = ((matrix_id >= args.id_min) and (matrix_id <= args.id_max))
49                isSquare = (row[2] == row[3])
50                isReal = (row[5] == '1')
51
52                if (isInBounds and isSquare and isReal):
53                    if args.ids is None or matrix_id in args.ids:
54                        matrix_name = row[0] + '/' + row[1] + '.tar.gz'
55                        gzip_path = matrix_dir + row[0] + '_' + row[1] + '.tar.gz'
56                        matrix_path = matrix_dir + row[1] + '/' + row[1] + ".mtx"
57
58                        matrix_exists = os.path.isfile(gzip_path)
59                        if matrix_exists:
60                            tar = tarfile.open(gzip_path, mode='r:gz')
61                            matrix_files = tar.getnames()
62                        else:
63                            # Download matrix if it doesn't exist
64                            print("Downloading " + matrix_name)
65                            sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
66                            testfile = urllib.URLopener(context=sslcontext)
67                            testfile.retrieve("https://sparse.tamu.edu/MM/" + matrix_name, gzip_path)
68                            tar = tarfile.open(gzip_path, mode='r:gz')
69                            tar.extractall(path=matrix_dir) # Extract the matrix from the tar.gz file
70                            tar.close()
71
72                        # Determine which test executables to run
73                        if args.tests == 'all':
74                            print("Calling ALL Tests...")
75                        if args.tests == 'memory' or args.tests == 'all':
76                            print("Calling Memory Test...")
77                            status = call(["./tests/mongoose_test_memory", matrix_path])
78                            if status:
79                                print("Error! Memory Test Failure")
80                                cleanup(args, row, matrix_exists, gzip_path)
81                                sys.exit(status)
82                        if args.tests == 'valgrind' or args.tests == 'all':
83                            print("Calling Valgrind Test...")
84                            valgrind = find_executable('valgrind')
85                            if valgrind:
86                                status = call([valgrind + " --leak-check=full ./tests/mongoose_test_memory", matrix_path])
87                                if status:
88                                    print("Error! Valgrind Test Failure")
89                                    cleanup(args, row, matrix_exists, gzip_path)
90                                    sys.exit(status)
91                            else:
92                                print("\033[91mERROR!\033[0m Unable to find Valgrind. Skipping Valgrind Test...")
93                        if args.tests == 'io' or args.tests == 'all':
94                            print("Calling I/O Test...")
95                            status = call(["./tests/mongoose_test_io", matrix_path, "1"])
96                            if status:
97                                print("Error! I/O Test Failure")
98                                cleanup(args, row, matrix_exists, gzip_path)
99                                sys.exit(status)
100                        if args.tests == 'edgesep' or args.tests == 'all':
101                            print("Calling Edge Separator Test...")
102                            target_split = args.target_split
103                            status = call(["./tests/mongoose_test_edgesep", matrix_path, str(target_split)])
104                            if status:
105                                print("Error! Edge Separator Test Failure")
106                                cleanup(args, row, matrix_exists, gzip_path)
107                                sys.exit(status)
108                        if args.tests == 'performance' or args.tests == 'all':
109                            print("Calling Performance Test...")
110                            status = call(["./tests/mongoose_test_performance", matrix_path, row[0] + '_' + row[1] + '_performance.txt'])
111                            if status:
112                                print("Error! Performance Test Failure")
113                                cleanup(args, row, matrix_exists, gzip_path)
114                                sys.exit(status)
115
116                        # Delete the matrix only if we downloaded it and the keep
117                        # flag is off
118                        cleanup(args, row, matrix_exists, gzip_path)
119
120
121    # Delete the ssstats.csv file now that we're done
122    os.remove(stats_file)
123
124def cleanup(args, row, matrix_exists, gzip_path):
125    matrix_dir = args.matrix_directory
126
127    if args.purge or not (args.keep or matrix_exists):
128        files = os.listdir(matrix_dir + row[1])
129        for file in files:
130            os.remove(os.path.join(matrix_dir + row[1] + '/', file))
131        os.rmdir(matrix_dir + row[1])
132        os.remove(gzip_path)
133
134def getMatrixDirectory(args):
135    # Check if the supplied matrix download directory exists - if not, create it
136    matrix_dir = args.matrix_directory
137    if not os.path.exists(matrix_dir):
138        os.makedirs(matrix_dir)
139
140    # Make sure the directory ends with '/'
141    if not matrix_dir.endswith('/'):
142        matrix_dir = matrix_dir + '/'
143
144    return matrix_dir
145
146def downloadStatsFile(matrix_dir):
147    sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
148    downloader = urllib.URLopener(context=sslcontext)
149    downloader.retrieve(
150        "https://sparse.tamu.edu/files/ssstats.csv",
151        matrix_dir + "/ssstats.csv")
152
153    stats_file = matrix_dir + "/ssstats.csv"
154    return stats_file
155
156def runCoverageUtility(args):
157    if args.gcov:
158        gcov = args.gcov
159    else:
160        gcov = find_executable('gcov')
161
162    if gcov:
163        # Determine if we are using GCC gcov or LLVM gcov
164        gcov_version = check_output([gcov, "--version"])
165        if gcov_version.find('LLVM') == -1:
166            call([gcov + " -o ./CMakeFiles/mongoose_lib_dbg.dir/Source ../Source/*.cpp"], shell=True)
167        else:
168            call(gcov + " -o=./CMakeFiles/mongoose_lib_dbg.dir/Source ../Source/*.cpp", shell=True)
169
170    gcovr = find_executable('gcovr')
171    if gcovr:
172        if args.html_coverage:
173            print("Running gcovr with HTML generation")
174            call([gcovr,
175                  "--html",
176                  "--html-details",
177                  "--output=coverage.html",
178                  "--gcov-executable=" + gcov,
179                  "--object-directory=CMakeFiles/mongoose_lib_dbg.dir/Source",
180                  "--root=../Source/"])
181        else:
182            print("Running gcovr without HTML generation")
183            call([gcovr, "--gcov-executable=" + gcov, "--object-directory=CMakeFiles/mongoose_lib_dbg.dir/Source", "--root=../Source/"])
184    else:
185        print("\033[91mERROR!\033[0m Cannot generate HTML coverage report, gcovr not found!")
186
187def parseArguments():
188    parser = argparse.ArgumentParser(
189                        description='Run tests on the Mongoose library.')
190    parser.add_argument('-k', '--keep',
191                        action='store_true',
192                        help='do not remove downloaded files when test is complete')
193    parser.add_argument('-p', '--purge',
194                        action='store_true',
195                        help='force remove downloaded matrix files when complete')
196    parser.add_argument('-min',
197                        action='store',
198                        metavar='min_id',
199                        type=int,
200                        default=1,
201                        dest='id_min',
202                        help='minimum matrix ID to run tests on [default: 1]')
203    parser.add_argument('-max',
204                        action='store',
205                        metavar='max_id',
206                        type=int,
207                        default=2757,
208                        dest='id_max',
209                        help='maximum matrix ID to run tests on [default: 2757]')
210    parser.add_argument('-i', '--ids',
211                        action='store',
212                        nargs='+',
213                        metavar='matrix_ID',
214                        type=int,
215                        help='list of matrix IDs to run tests on')
216    parser.add_argument('-t', '--tests',
217                        choices=['all', 'memory', 'io', 'edgesep', 'performance', 'valgrind', 'none'],
218                        default='none',
219                        help='choice of which tests to run')
220    parser.add_argument('-s', '--split',
221                        action='store',
222                        metavar='target_split',
223                        type=float,
224                        dest='target_split',
225                        default=0.5,
226                        help='target split ratio for edge separator test [default: 0.5]')
227    parser.add_argument('-d', '--matrix-directory',
228                        action='store',
229                        metavar='matrix_dir',
230                        default='../Matrix/',
231                        help='download directory for Matrix Market data files')
232    parser.add_argument('-c', '--coverage',
233                        action='store_true',
234                        help='generate coverage information')
235    parser.add_argument('--html-coverage',
236                        action='store_true',
237                        help='generate html coverage pages if gcovr is available')
238    parser.add_argument('--gcov',
239                        action='store',
240                        metavar='gcov_path',
241                        help='path to gcov tool')
242
243    return parser.parse_args()
244
245def checkLocation():
246    # Check if we are in the right location - if not, exit
247    if not (os.path.isdir('./CMakeFiles/mongoose_lib_dbg.dir/Source')
248            and os.path.isdir('../Source')):
249        print(
250            "\n\033[91mERROR!\033[0m Looks like you might not be running this from "
251            "your build directory.\n\n"
252            "Make sure that... \n\n"
253            "  * You are in your build directory (e.g. Mongoose/build) and\n"
254            "  * You have built Mongoose ('cmake ..' followed by 'make')\n")
255        sys.exit()
256
257    # Check if Mongoose has been built - if not, exit
258    if not (os.path.exists('./CMakeFiles/mongoose_lib_dbg.dir/Source/Mongoose_Graph.o')):
259        print(
260            "\n\033[91mERROR!\033[0m Looks like you might not have built Mongoose "
261            "yet.\n\n"
262            "Make sure that... \n\n"
263            "  * You are in your build directory (e.g. Mongoose/build) and\n"
264            "  * You have built Mongoose ('cmake ..' followed by 'make')\n")
265        sys.exit()
266
267if __name__=="__main__":
268   main()
269