Merge pull request #3370 from dokku/shellcheck-junit-output
Add junit support to shellcheck output
This commit is contained in:
40
tests.mk
40
tests.mk
@@ -1,5 +1,16 @@
|
||||
SYSTEM := $(shell sh -c 'uname -s 2>/dev/null')
|
||||
|
||||
bats:
|
||||
ifeq ($(SYSTEM),Darwin)
|
||||
ifneq ($(shell bats --version > /dev/null 2>&1 ; echo $$?),0)
|
||||
brew install bats-core
|
||||
endif
|
||||
else
|
||||
git clone https://github.com/josegonzalez/bats-core.git /tmp/bats
|
||||
cd /tmp/bats && sudo ./install.sh /usr/local
|
||||
rm -rf /tmp/bats
|
||||
endif
|
||||
|
||||
shellcheck:
|
||||
ifneq ($(shell shellcheck --version > /dev/null 2>&1 ; echo $$?),0)
|
||||
ifeq ($(SYSTEM),Darwin)
|
||||
@@ -11,7 +22,16 @@ else
|
||||
endif
|
||||
endif
|
||||
|
||||
ci-dependencies: shellcheck bats
|
||||
xmlstarlet:
|
||||
ifneq ($(shell xmlstarlet --version > /dev/null 2>&1 ; echo $$?),0)
|
||||
ifeq ($(SYSTEM),Darwin)
|
||||
brew install xmlstarlet
|
||||
else
|
||||
sudo apt-get update -qq && sudo apt-get install -qq -y xmlstarlet
|
||||
endif
|
||||
endif
|
||||
|
||||
ci-dependencies: shellcheck bats xmlstarlet
|
||||
|
||||
setup-deploy-tests:
|
||||
mkdir -p /home/dokku
|
||||
@@ -57,22 +77,12 @@ ifeq ($(shell grep dokku.me /home/dokku/VHOST 2>/dev/null),)
|
||||
echo "dokku.me" > /home/dokku/VHOST
|
||||
endif
|
||||
|
||||
bats:
|
||||
ifeq ($(SYSTEM),Darwin)
|
||||
ifneq ($(shell bats --version > /dev/null 2>&1 ; echo $$?),0)
|
||||
brew install bats-core
|
||||
endif
|
||||
else
|
||||
git clone https://github.com/josegonzalez/bats-core.git /tmp/bats
|
||||
cd /tmp/bats && sudo ./install.sh /usr/local
|
||||
rm -rf /tmp/bats
|
||||
endif
|
||||
|
||||
lint:
|
||||
# these are disabled due to their expansive existence in the codebase. we should clean it up though
|
||||
# SC2034: VAR appears unused - https://github.com/koalaman/shellcheck/wiki/SC2034
|
||||
@cat tests/shellcheck-exclude | sed -n -e '/^# SC/p'
|
||||
@echo linting...
|
||||
@$(QUIET) find . -not -path '*/\.*' -not -path './debian/*' -type f | xargs file | grep text | awk -F ':' '{ print $$1 }' | xargs head -n1 | egrep -B1 "bash" | grep "==>" | awk '{ print $$2 }' | xargs shellcheck -e SC2034
|
||||
@mkdir -p test-results/shellcheck
|
||||
@$(QUIET) find . -not -path '*/\.*' -not -path './debian/*' -type f | xargs file | grep text | awk -F ':' '{ print $$1 }' | xargs head -n1 | egrep -B1 "bash" | grep "==>" | awk '{ print $$2 }' | xargs shellcheck -e $(shell cat tests/shellcheck-exclude | sed -n -e '/^# SC/p' | cut -d' ' -f2 | paste -d, -s) | tests/shellcheck-to-junit --output test-results/shellcheck/results.xml
|
||||
|
||||
ci-go-coverage:
|
||||
docker run --rm -ti \
|
||||
@@ -209,6 +219,6 @@ deploy-tests:
|
||||
test: setup-deploy-tests lint unit-tests deploy-tests
|
||||
|
||||
test-ci:
|
||||
mkdir -p test-results/bats
|
||||
@mkdir -p test-results/bats
|
||||
@cd tests/unit && echo "executing tests: $(shell cd tests/unit ; circleci tests glob *.bats | circleci tests split --split-by=timings | xargs)"
|
||||
cd tests/unit && bats --formatter bats-format-junit -e -T -o ../../test-results/bats $(shell cd tests/unit ; circleci tests glob *.bats | circleci tests split --split-by=timings | xargs)
|
||||
|
||||
@@ -4,6 +4,7 @@ import glob
|
||||
import os
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
def mkdir_p(directory_name):
|
||||
try:
|
||||
os.makedirs(directory_name)
|
||||
@@ -11,6 +12,7 @@ def mkdir_p(directory_name):
|
||||
if exc.errno == errno.EEXIST and os.path.isdir(directory_name):
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
root_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..')
|
||||
test_results_path = os.path.realpath(os.path.join(root_path, 'test-results'))
|
||||
@@ -27,5 +29,6 @@ def main():
|
||||
mkdir_p(os.path.dirname(new_path))
|
||||
os.rename(file, new_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
29
tests/shellcheck-exclude
Normal file
29
tests/shellcheck-exclude
Normal file
@@ -0,0 +1,29 @@
|
||||
# SC1001 - This \/ will be a regular '/' in this context - https://github.com/koalaman/shellcheck/wiki/SC1001
|
||||
# SC1003 - Want to escape a single quote? echo 'This is how it'\''s done' - https://github.com/koalaman/shellcheck/wiki/SC1003
|
||||
# SC1090 - Can't follow non-constant source. Use a directive to specify location - https://github.com/koalaman/shellcheck/wiki/SC1090
|
||||
# SC1091 - Not following: FILE: does not exist (No such file or directory) - https://github.com/koalaman/shellcheck/wiki/SC1091
|
||||
# SC1117 - Backslash is literal in "\n". Prefer explicit escaping: "\\n" - https://github.com/koalaman/shellcheck/wiki/SC1117
|
||||
# SC2029 - Note that, unescaped, this expands on the client side - https://github.com/koalaman/shellcheck/wiki/SC2029
|
||||
# SC2034 - VAR appears unused - https://github.com/koalaman/shellcheck/wiki/SC2034
|
||||
# SC2046 - Quote this to prevent word splitting - https://github.com/koalaman/shellcheck/wiki/SC2046
|
||||
# SC2068 - Double quote array expansions to avoid re-splitting elements - https://github.com/koalaman/shellcheck/wiki/SC2068
|
||||
# SC2086 - Double quote to prevent globbing and word splitting - https://github.com/koalaman/shellcheck/wiki/SC2086
|
||||
# SC2119 - Use 'function "$@"' if function's $1 should mean script's $1 - https://github.com/koalaman/shellcheck/wiki/SC2119
|
||||
# SC2120 - Function references arguments, but none are ever passed - https://github.com/koalaman/shellcheck/wiki/SC2120
|
||||
# SC2128 - Expanding an array without an index only gives the first element - https://github.com/koalaman/shellcheck/wiki/SC2128
|
||||
# SC2148 - Tips depend on target shell and yours is unknown. Add a shebang - https://github.com/koalaman/shellcheck/wiki/SC2148
|
||||
# SC2153 - Possible misspelling: DOKKU_ROOT may not be assigned, but DOKKU_PORT is - https://github.com/koalaman/shellcheck/wiki/SC2153
|
||||
# SC2154 - passed_docker_option is referenced but not assigned - https://github.com/koalaman/shellcheck/wiki/SC2154
|
||||
# SC2155 - Declare and assign separately to avoid masking return values - https://github.com/koalaman/shellcheck/wiki/SC2155
|
||||
# SC2162 - read without -r will mangle backslashes - https://github.com/koalaman/shellcheck/wiki/SC2162
|
||||
# SC2174 - When used with -p, -m only applies to the deepest directory - https://github.com/koalaman/shellcheck/wiki/SC2174
|
||||
# SC2179 - Use array+=("item") to append items to an array - https://github.com/koalaman/shellcheck/wiki/SC2179
|
||||
# SC2191 - The = here is literal. To assign by index, use ( [index]=value ) with no spaces. To keep as literal, quote it - https://github.com/koalaman/shellcheck/wiki/SC2191
|
||||
# SC2196 - egrep is non-standard and deprecated. Use grep -E instead - https://github.com/koalaman/shellcheck/wiki/SC2196
|
||||
# SC2199 - Arrays implicitly concatenate in [[ ]]. Use a loop (or explicit * instead of @) - https://github.com/koalaman/shellcheck/wiki/SC2199
|
||||
# SC2207 - Prefer mapfile or read -a to split command output (or quote to avoid splitting) - https://github.com/koalaman/shellcheck/wiki/SC2207
|
||||
# SC2219 - Instead of 'let expr', prefer (( expr )) - https://github.com/koalaman/shellcheck/wiki/SC2219
|
||||
# SC2220 - Invalid flags are not handled. Add a *) case - https://github.com/koalaman/shellcheck/wiki/SC2220
|
||||
# SC2230 - which is non-standard. Use builtin 'command -v' instead - https://github.com/koalaman/shellcheck/wiki/SC2230
|
||||
# SC2231 - Quote expansions in this for loop glob to prevent wordsplitting, e.g. "$dir"/*.txt - https://github.com/koalaman/shellcheck/wiki/SC2231
|
||||
# SC2235 - Use { ..; } instead of (..) to avoid subshell overhead - https://github.com/koalaman/shellcheck/wiki/SC2235
|
||||
160
tests/shellcheck-to-junit
Executable file
160
tests/shellcheck-to-junit
Executable file
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import argparse
|
||||
import collections
|
||||
import datetime
|
||||
import re
|
||||
import socket
|
||||
import sys
|
||||
|
||||
from xml.etree import ElementTree
|
||||
|
||||
|
||||
def CDATA(text=None):
|
||||
element = ElementTree.Element('![CDATA[')
|
||||
element.text = text
|
||||
return element
|
||||
|
||||
|
||||
def _serialize_xml(write, elem, qnames, namespaces,short_empty_elements, **kwargs):
|
||||
|
||||
if elem.tag == '![CDATA[':
|
||||
write("\n<{}{}]]>\n".format(elem.tag, elem.text))
|
||||
if elem.tail:
|
||||
write(ElementTree._escape_cdata(elem.tail))
|
||||
else:
|
||||
return ElementTree._original_serialize_xml(write, elem, qnames, namespaces,short_empty_elements, **kwargs)
|
||||
|
||||
|
||||
ElementTree._original_serialize_xml = ElementTree._serialize_xml
|
||||
ElementTree._serialize_xml = ElementTree._serialize['xml'] = _serialize_xml
|
||||
|
||||
|
||||
def read_in():
|
||||
lines = sys.stdin.readlines()
|
||||
for i in range(len(lines)):
|
||||
lines[i] = lines[i].rstrip()
|
||||
return lines
|
||||
|
||||
|
||||
def process_lines(lines):
|
||||
files = {}
|
||||
current_file = None
|
||||
previous_line = None
|
||||
line_no = None
|
||||
new_issues = []
|
||||
code = None
|
||||
|
||||
RE_VIOLATION = re.compile(r"\^-- (SC[\w]+): (.*)")
|
||||
|
||||
for line in lines:
|
||||
# start a new block
|
||||
if line == '':
|
||||
if current_file is not None:
|
||||
file_data = files.get(current_file, {})
|
||||
files[current_file] = file_data
|
||||
|
||||
issue_data = file_data.get(line_no, {})
|
||||
issue_data['code'] = code
|
||||
files[current_file][line_no] = issue_data
|
||||
|
||||
issues = issue_data.get('issues', [])
|
||||
issues.extend(new_issues)
|
||||
issue_data['issues'] = issues
|
||||
|
||||
files[current_file][line_no] = issue_data
|
||||
|
||||
code = None
|
||||
current_file = None
|
||||
line_no = None
|
||||
elif line.startswith('In ./') and not previous_line:
|
||||
current_file = line.split(' ')[1].replace('./', '')
|
||||
line_no = line.split(' ')[3]
|
||||
new_issues = []
|
||||
code = None
|
||||
elif code is None and len(new_issues) == 0:
|
||||
code = line
|
||||
else:
|
||||
match = RE_VIOLATION.match(line.strip())
|
||||
if not match:
|
||||
print('Error: Issue parsing line "{0}"'.format(line.strip()))
|
||||
else:
|
||||
new_issues.append({
|
||||
'shellcheck_id': match.group(1),
|
||||
'message': match.group(2),
|
||||
'original_message': line
|
||||
})
|
||||
|
||||
previous_line = line
|
||||
|
||||
return files
|
||||
|
||||
|
||||
def output_junit(files, output=None):
|
||||
timestamp = datetime.datetime.now().replace(microsecond=0).isoformat()
|
||||
failures = 0
|
||||
for file, data in files.items():
|
||||
for line, issue_data in data.items():
|
||||
code = issue_data.get('code')
|
||||
for issue in issue_data.get('issues', []):
|
||||
failures += 1
|
||||
|
||||
root = ElementTree.Element("testsuite",
|
||||
name="shellcheck",
|
||||
tests="0",
|
||||
failures="{0}".format(failures),
|
||||
errors="0",
|
||||
skipped="0",
|
||||
timestamp=timestamp,
|
||||
time="0",
|
||||
hostname=socket.gethostname())
|
||||
|
||||
ElementTree.SubElement(root, "properties")
|
||||
for file, data in files.items():
|
||||
for line, issue_data in data.items():
|
||||
code = issue_data.get('code')
|
||||
for issue in issue_data.get('issues', []):
|
||||
testcase = ElementTree.SubElement(root,
|
||||
"testcase",
|
||||
classname=file,
|
||||
name=file,
|
||||
time="0")
|
||||
shellcheck_id = issue.get('shellcheck_id')
|
||||
message = 'line {0}: {1}'.format(line, issue.get('message'))
|
||||
original_message = issue.get('original_message')
|
||||
e = ElementTree.Element("failure",
|
||||
type=shellcheck_id,
|
||||
message=message)
|
||||
cdata = CDATA("\n".join([code, original_message]))
|
||||
e.append(cdata)
|
||||
testcase.append(e)
|
||||
|
||||
ElementTree.SubElement(root, "system-out")
|
||||
ElementTree.SubElement(root, "system-err")
|
||||
|
||||
content = ElementTree.tostring(root, encoding='UTF-8', method='xml')
|
||||
if output:
|
||||
with open(output, 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Process shellcheck output to junit.')
|
||||
parser.add_argument('--output',
|
||||
dest='output',
|
||||
action='store',
|
||||
default=None,
|
||||
help='file to write shellcheck output')
|
||||
args = parser.parse_args()
|
||||
|
||||
lines = read_in()
|
||||
files = process_lines(lines)
|
||||
files = collections.OrderedDict(sorted(files.items()))
|
||||
output_junit(files, args.output)
|
||||
for line in lines:
|
||||
print(line)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user