Home | History | Annotate | Download | only in verify
      1 # -*- coding: utf-8 -*-
      2 
      3 #-------------------------------------------------------------------------
      4 # Khronos OpenGL CTS
      5 # ------------------
      6 #
      7 # Copyright (c) 2016 The Khronos Group Inc.
      8 #
      9 # Licensed under the Apache License, Version 2.0 (the "License");
     10 # you may not use this file except in compliance with the License.
     11 # You may obtain a copy of the License at
     12 #
     13 #      http://www.apache.org/licenses/LICENSE-2.0
     14 #
     15 # Unless required by applicable law or agreed to in writing, software
     16 # distributed under the License is distributed on an "AS IS" BASIS,
     17 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     18 # See the License for the specific language governing permissions and
     19 # limitations under the License.
     20 #
     21 #-------------------------------------------------------------------------
     22 
     23 import os
     24 import sys
     25 import xml.dom.minidom
     26 import re
     27 import subprocess
     28 
     29 ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))
     30 sys.path.append(os.path.join(ROOT_DIR, "scripts", "verify"))
     31 sys.path.append(os.path.join(ROOT_DIR, "scripts", "build"))
     32 sys.path.append(os.path.join(ROOT_DIR, "scripts", "log"))
     33 
     34 from package import getPackageDescription
     35 from verify import *
     36 from message import *
     37 from common import *
     38 from log_parser import *
     39 from summary import *
     40 
     41 def getConfigCaseName (type):
     42 	configs = { "es32" : ["CTS-Configs.es32", "CTS-Configs.es31", "CTS-Configs.es3", "CTS-Configs.es2"],
     43 				"es31" : ["CTS-Configs.es31", "CTS-Configs.es3", "CTS-Configs.es2"],
     44 				"es3"  : ["CTS-Configs.es3", "CTS-Configs.es2"],
     45 				"es2"  : ["CTS-Configs.es2"]}
     46 	return configs[type]
     47 
     48 def retrieveReportedConfigs(caseName, log):
     49 	doc				= xml.dom.minidom.parseString(log)
     50 	sectionItems	= doc.getElementsByTagName('Section')
     51 	sectionName		= None
     52 
     53 	configs = []
     54 	for sectionItem in sectionItems:
     55 		sectionName	= sectionItem.getAttributeNode('Name').nodeValue
     56 		if sectionName == "Configs":
     57 			assert len(configs) == 0
     58 			textItems = sectionItem.getElementsByTagName('Text')
     59 			for textItem in textItems:
     60 				configs.append(getNodeText(textItem))
     61 	res = {caseName : configs}
     62 	return res
     63 
     64 def compareConfigs(filename, baseConfigs, cmpConfigs):
     65 	messages = []
     66 	assert len(list(baseConfigs.keys())) == 1
     67 	assert len(list(cmpConfigs.keys())) == 1
     68 	baseKey = list(baseConfigs.keys())[0]
     69 	cmpKey = list(cmpConfigs.keys())[0]
     70 
     71 	if cmp(baseConfigs[baseKey], cmpConfigs[cmpKey]) != 0:
     72 		messages.append(error(filename, "Confomant configs reported for %s and %s do not match" % (baseKey,cmpKey)))
     73 
     74 	return messages
     75 
     76 def verifyConfigFile (filename, type):
     77 	messages  = []
     78 	caseNames = getConfigCaseName(type)
     79 
     80 	parser		= BatchResultParser()
     81 	results		= parser.parseFile(filename)
     82 	baseConfigs	= None
     83 
     84 	for caseName in caseNames:
     85 		caseResult	= None
     86 		print "Verifying %s in %s" % (caseName, filename)
     87 		for result in results:
     88 			if result.name == caseName:
     89 				caseResult = result
     90 				break;
     91 		if caseResult == None:
     92 			messages.append(error(filename, "Missing %s" % caseName))
     93 		else:
     94 			configs = retrieveReportedConfigs(caseName, result.log)
     95 			if baseConfigs == None:
     96 				baseConfigs = configs
     97 			else:
     98 				messages += compareConfigs(filename, baseConfigs, configs)
     99 			if not caseResult.statusCode in ALLOWED_STATUS_CODES:
    100 				messages.append(error(filename, "%s failed" % caseResult))
    101 
    102 	return messages
    103 
    104 def verifyMustpassCases(package, mustpassCases, type):
    105 	messages = []
    106 	apiToTest = { "es32" : ["gles32", "gles31", "gles3", "gles2", "egl"],
    107 				"es31" : ["gles31", "gles3", "gles2", "egl"],
    108 				"es3"  : ["gles3", "gles2", "egl"],
    109 				"es2"  : ["gles2", "egl"]}
    110 
    111 	for mustpass in mustpassCases:
    112 		mustpassXML = os.path.join(mustpass, "mustpass.xml")
    113 		doc = xml.dom.minidom.parse(mustpassXML)
    114 		testConfigs = doc.getElementsByTagName("Configuration")
    115 		# check that all configs that must be tested are present
    116 		for testConfig in testConfigs:
    117 			caseListFile = testConfig.getAttributeNode("caseListFile").nodeValue
    118 			# identify APIs that must be tested for the given type
    119 			apis = apiToTest[type]
    120 			# identify API tested by the current config
    121 			configAPI = caseListFile.split('-')[0]
    122 			if configAPI in apis:
    123 				# the API in this config is expected to be tested
    124 				mustTest = True
    125 			else:
    126 				mustTest = False
    127 			pattern = "config-" + os.path.splitext(caseListFile)[0] + "-cfg-[0-9]*"+"-run-[0-9]*"
    128 			cmdLine = testConfig.getAttributeNode("commandLine").nodeValue
    129 			cfgItems = {'height':None, 'width':None, 'seed':None, 'rotation':None}
    130 			for arg in cmdLine.split():
    131 				val = arg.split('=')[1]
    132 				if "deqp-surface-height" in arg:
    133 					cfgItems['height'] = val
    134 				elif "deqp-surface-width" in arg:
    135 					cfgItems['width'] = val
    136 				elif "deqp-base-seed" in arg:
    137 					cfgItems['seed'] = val
    138 				elif "deqp-screen-rotation" in arg:
    139 					cfgItems['rotation'] = val
    140 			pattern += "-width-" + cfgItems['width'] + "-height-" + cfgItems['height']
    141 			if cfgItems['seed'] != None:
    142 				pattern += "-seed-" + cfgItems['seed']
    143 			pattern += ".qpa"
    144 			p = re.compile(pattern)
    145 			matches = [m for l in mustpassCases[mustpass] for m in (p.match(l),) if m]
    146 			if len(matches) == 0 and mustTest == True:
    147 					conformOs = testConfig.getAttributeNode("os").nodeValue
    148 					txt = "Configuration %s %s was not executed" % (caseListFile, cmdLine)
    149 					if conformOs == "any" or (package.conformOs != None and conformOs in package.conformOs.lower()):
    150 						msg = error(mustpassXML, txt)
    151 					else:
    152 						msg = warning(mustpassXML, txt)
    153 					messages.append(msg)
    154 			elif len(matches) != 0 and mustTest == False:
    155 				messages.append(error(mustpassXML, "Configuration %s %s was not expected to be tested but present in cts-run-summary.xml" % (caseListFile, cmdLine)))
    156 
    157 	return messages
    158 
    159 def verifyTestLogs (package):
    160 	messages = []
    161 
    162 	try:
    163 		execute(['git', 'checkout', '--quiet', package.conformVersion])
    164 	except Exception, e:
    165 		print str(e)
    166 		print "Failed to checkout release tag %s." % package.conformVersion
    167 		return messages
    168 
    169 	messages = []
    170 	summary	= parseRunSummary(os.path.join(package.basePath, package.summary))
    171 	mustpassDirs = []
    172 
    173 	# Check Conformant attribute
    174 	if not summary.isConformant:
    175 		messages.append(error(package.summary, "Runner reported conformance failure (Conformant=\"False\" in <Summary>)"))
    176 
    177 	# Verify config list
    178 	messages += verifyConfigFile(os.path.join(package.basePath, summary.configLogFilename), summary.type)
    179 
    180 	mustpassCases = {}
    181 	# Verify that all run files passed
    182 	for runLog in summary.runLogAndCaselist:
    183 		sys.stdout.write("Verifying %s -" % runLog)
    184 		sys.stdout.flush()
    185 
    186 		mustpassFile = os.path.join(ROOT_DIR, "external", "openglcts", summary.runLogAndCaselist[runLog])
    187 		key = os.path.dirname(mustpassFile)
    188 		if key in mustpassCases:
    189 			mpCase = mustpassCases[key]
    190 		else:
    191 			mpCase = []
    192 		mpCase.append(runLog)
    193 		mustpassCases[os.path.dirname(mustpassFile)] = mpCase
    194 		mustpass = readMustpass(mustpassFile)
    195 		messages_log = verifyTestLog(os.path.join(package.basePath, runLog), mustpass)
    196 
    197 		errors	= [m for m in messages_log if m.type == ValidationMessage.TYPE_ERROR]
    198 		warnings	= [m for m in messages_log if m.type == ValidationMessage.TYPE_WARNING]
    199 		if len(errors) > 0:
    200 			sys.stdout.write(" finished with ERRRORS")
    201 		if len(warnings) > 0:
    202 			sys.stdout.write(" finished with WARNINGS")
    203 		if len(errors) == 0 and len(warnings) == 0:
    204 			sys.stdout.write(" OK")
    205 		sys.stdout.write("\n")
    206 		sys.stdout.flush()
    207 
    208 		messages += messages_log
    209 
    210 	messages += verifyMustpassCases(package, mustpassCases, summary.type)
    211 
    212 	return messages
    213 
    214 def verifyGitStatusFiles (package):
    215 	messages = []
    216 
    217 	if len(package.gitStatus) != 2:
    218 		messages.append(error(package.basePath, "Exactly two git status files must be present, found %s" % len(package.gitStatus)))
    219 
    220 	messages += verifyGitStatus(package)
    221 
    222 	return messages
    223 
    224 def isGitLogFileEmpty (package, modulePath, gitLog):
    225 	logPath	= os.path.join(package.basePath, gitLog)
    226 	log		= readFile(logPath)
    227 
    228 	args = ['git', 'log', '-1', package.conformVersion]
    229 	process = subprocess.Popen(args, cwd=modulePath, stdout=subprocess.PIPE)
    230 	output = process.communicate()[0]
    231 	if process.returncode != 0:
    232 		raise Exception("Failed to execute '%s', got %d" % (str(args), process.returncode))
    233 
    234 	return log == output
    235 
    236 def verifyGitLogFile (package):
    237 	messages = []
    238 
    239 	if len(package.gitLog) > 0:
    240 		for log, path in package.gitLog:
    241 			try:
    242 				isEmpty = isGitLogFileEmpty(package, path, log)
    243 			except Exception, e:
    244 				print str(e)
    245 				isEmpty = False
    246 
    247 			if not isEmpty:
    248 				messages.append(warning(os.path.join(package.basePath, log), "Log is not empty"))
    249 	else:
    250 		messages.append(error(package.basePath, "Missing git log files"))
    251 
    252 	return messages
    253 
    254 def verifyPatchFiles (package):
    255 	messages	= []
    256 	hasPatches	= len(package.patches)
    257 	logEmpty	= True
    258 	for log, path in package.gitLog:
    259 		logEmpty &= isGitLogFileEmpty(package, path, log)
    260 
    261 	if hasPatches and logEmpty:
    262 		messages.append(error(package.basePath, "Package includes patches but log is empty"))
    263 	elif not hasPatches and not logEmpty:
    264 		messages.append(error(package.basePath, "Test log is not empty but package doesn't contain patches"))
    265 
    266 	return messages
    267 
    268 def verifyGitLogFiles (package):
    269 	messages = []
    270 
    271 	if len(package.gitLog) != 2:
    272 		messages.append(error(package.basePath, "Exactly two git log file must be present, found %s" % len(package.gitLog)))
    273 
    274 	for i, gitLog in enumerate(package.gitLog):
    275 		if "kc-cts" in gitLog[0]:
    276 			package.gitLog[i] = gitLog[:1] + ("external/kc-cts/src",) + gitLog[2:]
    277 
    278 	messages += verifyGitLogFile(package)
    279 
    280 	return messages
    281 
    282 def verifyPackage (package):
    283 	messages = []
    284 
    285 	messages += verifyStatement(package)
    286 	messages += verifyGitStatusFiles(package)
    287 	messages += verifyGitLogFiles(package)
    288 	messages += verifyPatchFiles(package)
    289 
    290 	for item in package.otherItems:
    291 		messages.append(warning(os.path.join(package.basePath, item), "Unknown file"))
    292 
    293 	return messages
    294 
    295 def verifyESSubmission(argv):
    296 	if len(argv) != 2:
    297 		print "%s: [extracted submission package directory]" % sys.argv[0]
    298 		sys.exit(-1)
    299 	try:
    300 		execute(['git', 'ls-remote', 'origin', '--quiet'])
    301 	except Exception, e:
    302 		print str(e)
    303 		print "This script must be executed inside VK-GL-CTS directory."
    304 		sys.exit(-1)
    305 
    306 	packagePath		=  os.path.normpath(sys.argv[1])
    307 	package			=  getPackageDescription(packagePath)
    308 	messages		=  verifyPackage(package)
    309 	messages		+= verifyTestLogs(package)
    310 
    311 	errors			= [m for m in messages if m.type == ValidationMessage.TYPE_ERROR]
    312 	warnings		= [m for m in messages if m.type == ValidationMessage.TYPE_WARNING]
    313 
    314 	for message in messages:
    315 		print str(message)
    316 
    317 	print ""
    318 
    319 	if len(errors) > 0:
    320 		print "Found %d validation errors and %d warnings!" % (len(errors), len(warnings))
    321 		sys.exit(-2)
    322 	elif len(warnings) > 0:
    323 		print "Found %d warnings, manual review required" % len(warnings)
    324 		sys.exit(-1)
    325 	else:
    326 		print "All validation checks passed"
    327 
    328 if __name__ == "__main__":
    329 	verifyESSubmission(sys.argv)
    330