Jacoco coverage support for bazel
Change-Id: Ic94304aa6fed0e18f16cecfdda388d421064d0b6
diff --git a/tools/build/onos-prepare-sonar b/tools/build/onos-prepare-sonar
index baa7888..758693a 100755
--- a/tools/build/onos-prepare-sonar
+++ b/tools/build/onos-prepare-sonar
@@ -1,129 +1,262 @@
#!/usr/bin/env python
-# This script prepares this ONOS directory so that the Sonar Scanner can be run.
-# - Build ONOS
-# - Run tests on a per module basis, stage surefire-reports and jacoco.exec
-# - Generate sonar-project.properties file
+"""
+This script prepares this ONOS directory so that the Sonar Scanner can be run.
+ - Build ONOS
+ - Run coverage tests on a per module basis, stage surefire-reports and jacoco.exec
+ - Generate sonar-project.properties file
+"""
-import json
import os
+import sys
+import fnmatch
-from shutil import copy, copytree, rmtree
-from subprocess import call, check_call, check_output
+from shutil import copy, rmtree
+from subprocess import check_output, STDOUT, CalledProcessError
-# FIXME pull the version from the Buck version file
ONOS_VERSION = '2.0.0-SNAPSHOT'
-# SonarQube property file name and template
-FILE_NAME = 'sonar-project.properties'
+GENFILES = 'bazel-genfiles'
+SONAR_PROJECT = GENFILES + '/sonar-project'
+SUREFIRE_REPORTS = 'surefire-reports'
+SONAR_PROPERTIES_FILE_NAME = SONAR_PROJECT + '/sonar-project.properties'
+
+# Template for the sonar properties file
ROOT_TEMPLATE = '''# Auto-generated properties file
sonar.projectKey=%(key)s
sonar.projectName=%(name)s
sonar.projectVersion=%(version)s
-#sonar.sources=src
sonar.sourceEncoding=UTF-8
sonar.java.target = 1.8
sonar.java.source = 1.8
sonar.language=java
+sonar.junit.reportsPath = surefire-reports
+sonar.jacoco.reportPath = jacoco.exec
sonar.modules=%(modules)s
'''
-black_list = ["//protocols/grpc:grpc-core-repkg",
- "//apps/openstacktelemetry:grpc-core-repkg",
- "//web/gui2:_onos-gui2-base-jar"]
-# Change to $ONOS_ROOT
-ONOS_ROOT = os.environ['ONOS_ROOT']
-if ONOS_ROOT:
- os.chdir(ONOS_ROOT)
-
-
-def splitTarget(target):
+def split_target(target):
path, module = target.split(':', 2)
path = path.replace('//', '', 1)
return path, module
-def runCmd(cmd):
+def run_command(cmd):
output = check_output(cmd).rstrip()
return output.split('\n') if output else []
-# build ONOS
-runCmd(["bazel", "build", "onos"])
+def run_command_with_stderr(cmd):
+ output = check_output(cmd, stderr=STDOUT).rstrip()
+ return output.split('\n') if output else []
-# Find all onos OSGi jar file rules
-targets = runCmd(["bazel", "query", "kind('_bnd', '//...')"])
-targets = [target for target in targets if not target in black_list]
-# Uncomment this for easier debugging of a single package
-# targets = ['//core/net:onos-core-net']
-# Find all tests associated with onos_jar rules
-# FIXME we may want to insert kind('java_test', testsof...)
-# output = runCmd([BUCK, 'query', '--json', "testsof('%s')"] + targets)
-# test_map = json.loads(output[0])
+def make_dirs(path):
+ try:
+ os.makedirs(path)
+ except OSError:
+ pass
-# Flatten the values in the test target map
-# test_targets = [t for ts in test_map.values() for t in ts]
-# print test_targets
-# Build run tests
-# print runCmd([BUCK, 'test', '--no-cache', '--code-coverage', '--no-results-cache'] + test_targets)
+def find_bazel_project():
+ return os.path.basename(os.getcwd())
-# Build the sonar rules for each target
-# sonar_files = runCmd([BUCK, 'build', '--show-output'] + ['%s-sonar' % t for t in (targets + test_targets)])
-# sonar_files = dict([i.split(' ') for i in sonar_files[1:]]) # drop the first line; it's boilerplate
-# print sonar_files
+
+def find_bazel_classes_directory(module_name, path):
+ return os.getcwd() + "/bazel-out/darwin-fastbuild/bin/" + path + \
+ "/_javac/" + module_name + "-native/lib" + module_name + "-native-class_classes"
+
+
+def capture_surefire_reports(module_path):
+ matches = []
+ for root, dirnames, filenames in os.walk('bazel-testlogs/' + module_path + '/src/test/java'):
+ for filename in fnmatch.filter(filenames, '*.xml'):
+ source_path = os.path.join(root, filename)
+ matches.append(source_path)
+
+ destination_path = \
+ SONAR_PROJECT + "/" + module_path + "/" + SUREFIRE_REPORTS + "/TEST-" + \
+ source_path.replace("bazel-testlogs/", "")\
+ .replace("/test.xml", "test.xml")\
+ .replace(module_path, "")\
+ .replace("/src/test/java/", "")\
+ .replace("/", ".")
+ make_dirs(path=os.path.dirname(destination_path))
+ copy(source_path, destination_path)
+
+
+def capture_jacoco(module_path):
+ source_path = '/tmp/jacoco.exec'
+ destination_path = \
+ SONAR_PROJECT + "/" + module_path
+ make_dirs(path=os.path.dirname(destination_path))
+ copy(source_path, destination_path)
+
+
+def capture_sources(module_path):
+ source_path = module_path + '/src'
+ destination_path = SONAR_PROJECT + "/" + module_path + '/src'
+ os.symlink(os.getcwd() + '/' + source_path, destination_path)
+
+
+"""
+ Writes out the properties for a given module and stages the files needed by the scanner.
+"""
def write_module(target, out):
- path, module_name = splitTarget(target)
+ path, module_name = split_target(target)
+ query = 'labels(srcs, "%s-native")' % target
+
+ # get rid of previous data
+ try:
+ os.remove('/tmp/jacoco.exec')
+ except OSError:
+ pass
+
+ try:
+ # Find all the test targets in this package
+ coverage_target_query = "attr(name, .*-coverage, tests(//" + path + ":*))"
+ coverage_targets_result = run_command(["bazel", "query", coverage_target_query])
+
+ # Find the test targets that are coverage targets
+ run_coverage_command = ['bazel', 'test']
+ for coverage_target in coverage_targets_result:
+ run_coverage_command.append(str(coverage_target))
+ except CalledProcessError:
+ print "Error querying test files for target " + target
+ return
+
+ try:
+ # Use bazel to run all the coverage targets
+ run_coverage_command.append('--cache_test_results=no')
+ run_command(run_coverage_command)
+
+ # Find the source files used by the tests
+ sources = run_command(['bazel', 'query', query])
+ except CalledProcessError as exc:
+ print "Error running test files for target " + target
+ raise exc
+
+ if not os.path.exists('/tmp/jacoco.exec'):
+ # No coverage data was produced, not much to do
+ return
+
+ # Filter out non-Java files
+ sources = \
+ [source_file for source_file in sources if "package-info" not in source_file and ".java" in source_file]
+
+ # Adjust source file paths to be relative to the root
+ sources_filtered = []
+ for source in sources:
+ sources_filtered.append(source.replace('//' + path + ':', "", 1))
+
+ # create a CSL of all the source files for use in the properties file
+ sources_csl = ",".join(sources_filtered).replace("//", "").replace(":", "/")
+
+ # Write out the properties for this package
out.write('%s.sonar.projectBaseDir=%s\n' % (module_name, path))
out.write('%(name)s.sonar.projectName=%(name)s\n' % {'name': module_name})
- query = 'labels(srcs, "%s-native")' % target
- sources = runCmd(['bazel', 'query', query])
- sources = [file for file in sources if "package-info" not in file and ".java" in file]
- print sources
- sources_csl = ",".join(sources).replace("//", ONOS_ROOT + "/").replace(":", "/")
out.write('%s.sonar.sources=%s\n' % (module_name, sources_csl))
+ binaries = find_bazel_classes_directory(module_name, path)
+ out.write('%s.sonar.java.binaries = %s\n' % (module_name, binaries))
- # tests = test_map[target] if target in test_map else []
+ # Get the dependencies for this package using bazel
+ deps_files = run_command_with_stderr(['bazel', 'build', '%s-tests-gen-deps' % target])
- # module_targets = [target] + tests
- # for property in [sonar_files[t+'-sonar'] for t in module_targets]:
- # print property
- # with open(property, 'r') as f:
- # for line in f.readlines():
- # out.write('%s.%s' % (module_name, line))
+ dep_file = ""
+ for source_file in deps_files:
+ if source_file.endswith(".txt"):
+ dep_file = source_file.strip()
+
+ libraries = []
+ with open(dep_file, 'r') as read_files:
+ lines = read_files.readline().split(',')
+
+ external_base_path = os.path.realpath(
+ os.path.realpath("bazel-" + find_bazel_project() + "/external") + "/../../..")
+ for line in lines:
+ library_file_name = line
+ if line.startswith("external"):
+ library_file_name = external_base_path + "/" + library_file_name
+ else:
+ library_file_name = os.getcwd() + "/" + library_file_name
+ libraries.append(library_file_name)
+
+ out.write('%s.sonar.java.libraries = %s\n' % (module_name, ",".join(libraries)))
+
+ # Capture files needed by the scanner into the staging area
+ capture_surefire_reports(path)
+ capture_jacoco(path)
+ capture_sources(path)
-# if tests:
-# rmtree(path + '/surefire-reports', ignore_errors=True)
-# rmtree('surefire-reports', ignore_errors=True)
-# runCmd([BUCK, 'test',
-# '--no-cache', '--no-results-cache',
-# '--code-coverage',
-# '--no-results-cache',
-# '--surefire-xml', 'surefire-reports'
-# ] + tests)
-# copy('buck-out/gen/jacoco/jacoco.exec', path)
-# #write jacoco.exec path to out; not needed.. this is the default
-# copytree('surefire-reports', path + '/surefire-reports')
-# rmtree('surefire-reports')
+def _main():
+ global ONOS_ROOT, targets
+ debug = False
+ if len(sys.argv) > 1:
+ debug = True
-# Write the sonar properties file
-with open(FILE_NAME, 'w') as out:
- out.write(ROOT_TEMPLATE % {
+ # Change to $ONOS_ROOT
+ ONOS_ROOT = os.environ['ONOS_ROOT']
+ if ONOS_ROOT:
+ os.chdir(ONOS_ROOT)
+
+ # build ONOS
+ run_command(["bazel", "build", "onos"])
+
+ # find the test targets to get coverage for
+ if debug:
+ # Use a predefined list of targets for debugging
+ targets = ['//core/net:onos-core-net', '//utils/misc:onlab-misc']
+ else:
+ # Query all onos OSGi jar file rules with tests from bazel
+ targets = run_command(["bazel", "query", "attr('name', '.*-tests-gen', '//...')"])
+ targets = [target.replace("-tests-gen-deps", "") for target in targets]
+
+ # Filter out targets without any tests in them
+ targets_with_tests = []
+ for target in targets:
+ colon = target.find(':')
+ base_target = target[0:colon]
+ target_query_result = run_command(['bazel', 'query', 'tests(' + base_target + ':*)'])
+ for result_line in target_query_result:
+ if "src/test" in result_line:
+ targets_with_tests.append(target)
+ break
+ targets = targets_with_tests
+
+ # Clear out any old results
+ rmtree(SONAR_PROJECT, True)
+
+ # make a directory for the new results
+ make_dirs(SONAR_PROJECT)
+
+ # fill in the template for the sonar properties
+ sonar_parameters = {
'name': 'onos',
'key': 'org.onosproject:onos',
'version': ONOS_VERSION,
- #'jacoco': '%s/buck-out/gen/jacoco/jacoco.exec' % ONOS_ROOT,
- 'modules': ','.join([splitTarget(t)[1] for t in targets])
- })
- for target in targets:
- print target
- write_module(target, out)
+ 'jacoco': '/tmp/jacoco.exec',
+ 'reports': 'surefire-reports',
+ 'modules': ','.join([split_target(t)[1] for t in targets])
+ }
+ if debug:
+ sonar_parameters["key"] = 'org.onosproject:onos-test-sonar'
+ sonar_parameters["name"] = 'onos-test-sonar'
+
+ # Write the sonar properties file
+ with open(SONAR_PROPERTIES_FILE_NAME, 'w') as out:
+ out.write(ROOT_TEMPLATE % sonar_parameters)
+ for target in targets:
+ print "Processing coverage for target " + target
+ write_module(target, out)
+
+
+if __name__ == "__main__":
+ _main()