aboutsummaryrefslogtreecommitdiff
path: root/contrib/devtools
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/devtools')
-rwxr-xr-xcontrib/devtools/check-rpc-mappings.py158
-rwxr-xr-xcontrib/devtools/git-subtree-check.sh37
-rwxr-xr-xcontrib/devtools/lint-all.sh22
-rwxr-xr-xcontrib/devtools/lint-whitespace.sh88
4 files changed, 295 insertions, 10 deletions
diff --git a/contrib/devtools/check-rpc-mappings.py b/contrib/devtools/check-rpc-mappings.py
new file mode 100755
index 0000000000..7e96852c5c
--- /dev/null
+++ b/contrib/devtools/check-rpc-mappings.py
@@ -0,0 +1,158 @@
+#!/usr/bin/env python3
+# Copyright (c) 2017 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+"""Check RPC argument consistency."""
+
+from collections import defaultdict
+import os
+import re
+import sys
+
+# Source files (relative to root) to scan for dispatch tables
+SOURCES = [
+ "src/rpc/server.cpp",
+ "src/rpc/blockchain.cpp",
+ "src/rpc/mining.cpp",
+ "src/rpc/misc.cpp",
+ "src/rpc/net.cpp",
+ "src/rpc/rawtransaction.cpp",
+ "src/wallet/rpcwallet.cpp",
+]
+# Source file (relative to root) containing conversion mapping
+SOURCE_CLIENT = 'src/rpc/client.cpp'
+# Argument names that should be ignored in consistency checks
+IGNORE_DUMMY_ARGS = {'dummy', 'arg0', 'arg1', 'arg2', 'arg3', 'arg4', 'arg5', 'arg6', 'arg7', 'arg8', 'arg9'}
+
+class RPCCommand:
+ def __init__(self, name, args):
+ self.name = name
+ self.args = args
+
+class RPCArgument:
+ def __init__(self, names, idx):
+ self.names = names
+ self.idx = idx
+ self.convert = False
+
+def parse_string(s):
+ assert s[0] == '"'
+ assert s[-1] == '"'
+ return s[1:-1]
+
+def process_commands(fname):
+ """Find and parse dispatch table in implementation file `fname`."""
+ cmds = []
+ in_rpcs = False
+ with open(fname, "r") as f:
+ for line in f:
+ line = line.rstrip()
+ if not in_rpcs:
+ if re.match("static const CRPCCommand .*\[\] =", line):
+ in_rpcs = True
+ else:
+ if line.startswith('};'):
+ in_rpcs = False
+ elif '{' in line and '"' in line:
+ m = re.search('{ *("[^"]*"), *("[^"]*"), *&([^,]*), *{([^}]*)} *},', line)
+ assert m, 'No match to table expression: %s' % line
+ name = parse_string(m.group(2))
+ args_str = m.group(4).strip()
+ if args_str:
+ args = [RPCArgument(parse_string(x.strip()).split('|'), idx) for idx, x in enumerate(args_str.split(','))]
+ else:
+ args = []
+ cmds.append(RPCCommand(name, args))
+ assert not in_rpcs and cmds, "Something went wrong with parsing the C++ file: update the regexps"
+ return cmds
+
+def process_mapping(fname):
+ """Find and parse conversion table in implementation file `fname`."""
+ cmds = []
+ in_rpcs = False
+ with open(fname, "r") as f:
+ for line in f:
+ line = line.rstrip()
+ if not in_rpcs:
+ if line == 'static const CRPCConvertParam vRPCConvertParams[] =':
+ in_rpcs = True
+ else:
+ if line.startswith('};'):
+ in_rpcs = False
+ elif '{' in line and '"' in line:
+ m = re.search('{ *("[^"]*"), *([0-9]+) *, *("[^"]*") *},', line)
+ assert m, 'No match to table expression: %s' % line
+ name = parse_string(m.group(1))
+ idx = int(m.group(2))
+ argname = parse_string(m.group(3))
+ cmds.append((name, idx, argname))
+ assert not in_rpcs and cmds
+ return cmds
+
+def main():
+ root = sys.argv[1]
+
+ # Get all commands from dispatch tables
+ cmds = []
+ for fname in SOURCES:
+ cmds += process_commands(os.path.join(root, fname))
+
+ cmds_by_name = {}
+ for cmd in cmds:
+ cmds_by_name[cmd.name] = cmd
+
+ # Get current convert mapping for client
+ client = SOURCE_CLIENT
+ mapping = set(process_mapping(os.path.join(root, client)))
+
+ print('* Checking consistency between dispatch tables and vRPCConvertParams')
+
+ # Check mapping consistency
+ errors = 0
+ for (cmdname, argidx, argname) in mapping:
+ try:
+ rargnames = cmds_by_name[cmdname].args[argidx].names
+ except IndexError:
+ print('ERROR: %s argument %i (named %s in vRPCConvertParams) is not defined in dispatch table' % (cmdname, argidx, argname))
+ errors += 1
+ continue
+ if argname not in rargnames:
+ print('ERROR: %s argument %i is named %s in vRPCConvertParams but %s in dispatch table' % (cmdname, argidx, argname, rargnames), file=sys.stderr)
+ errors += 1
+
+ # Check for conflicts in vRPCConvertParams conversion
+ # All aliases for an argument must either be present in the
+ # conversion table, or not. Anything in between means an oversight
+ # and some aliases won't work.
+ for cmd in cmds:
+ for arg in cmd.args:
+ convert = [((cmd.name, arg.idx, argname) in mapping) for argname in arg.names]
+ if any(convert) != all(convert):
+ print('ERROR: %s argument %s has conflicts in vRPCConvertParams conversion specifier %s' % (cmd.name, arg.names, convert))
+ errors += 1
+ arg.convert = all(convert)
+
+ # Check for conversion difference by argument name.
+ # It is preferable for API consistency that arguments with the same name
+ # have the same conversion, so bin by argument name.
+ all_methods_by_argname = defaultdict(list)
+ converts_by_argname = defaultdict(list)
+ for cmd in cmds:
+ for arg in cmd.args:
+ for argname in arg.names:
+ all_methods_by_argname[argname].append(cmd.name)
+ converts_by_argname[argname].append(arg.convert)
+
+ for argname, convert in converts_by_argname.items():
+ if all(convert) != any(convert):
+ if argname in IGNORE_DUMMY_ARGS:
+ # these are testing or dummy, don't warn for them
+ continue
+ print('WARNING: conversion mismatch for argument named %s (%s)' %
+ (argname, list(zip(all_methods_by_argname[argname], converts_by_argname[argname]))))
+
+ sys.exit(errors > 0)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/contrib/devtools/git-subtree-check.sh b/contrib/devtools/git-subtree-check.sh
index 2384d66cad..184951715e 100755
--- a/contrib/devtools/git-subtree-check.sh
+++ b/contrib/devtools/git-subtree-check.sh
@@ -18,7 +18,7 @@ find_latest_squash()
sub=
git log --grep="^git-subtree-dir: $dir/*\$" \
--pretty=format:'START %H%n%s%n%n%b%nEND%n' "$COMMIT" |
- while read a b junk; do
+ while read a b _; do
case "$a" in
START) sq="$b" ;;
git-subtree-mainline:) main="$b" ;;
@@ -41,21 +41,17 @@ find_latest_squash()
done
}
+# find latest subtree update
latest_squash="$(find_latest_squash "$DIR")"
if [ -z "$latest_squash" ]; then
echo "ERROR: $DIR is not a subtree" >&2
exit 2
fi
-
set $latest_squash
old=$1
rev=$2
-if [ "d$(git cat-file -t $rev 2>/dev/null)" != dcommit ]; then
- echo "ERROR: subtree commit $rev unavailable. Fetch/update the subtree repository" >&2
- exit 2
-fi
-tree_subtree=$(git show -s --format="%T" $rev)
-echo "$DIR in $COMMIT was last updated to upstream commit $rev (tree $tree_subtree)"
+
+# get the tree in the current commit
tree_actual=$(git ls-tree -d "$COMMIT" "$DIR" | head -n 1)
if [ -z "$tree_actual" ]; then
echo "FAIL: subtree directory $DIR not found in $COMMIT" >&2
@@ -69,9 +65,30 @@ if [ "d$tree_actual_type" != "dtree" ]; then
echo "FAIL: subtree directory $DIR is not a tree in $COMMIT" >&2
exit 1
fi
+
+# get the tree at the time of the last subtree update
+tree_commit=$(git show -s --format="%T" $old)
+echo "$DIR in $COMMIT was last updated in commit $old (tree $tree_commit)"
+
+# ... and compare the actual tree with it
+if [ "$tree_actual_tree" != "$tree_commit" ]; then
+ git diff $tree_commit $tree_actual_tree >&2
+ echo "FAIL: subtree directory was touched without subtree merge" >&2
+ exit 1
+fi
+
+# get the tree in the subtree commit referred to
+if [ "d$(git cat-file -t $rev 2>/dev/null)" != dcommit ]; then
+ echo "subtree commit $rev unavailable: cannot compare" >&2
+ exit
+fi
+tree_subtree=$(git show -s --format="%T" $rev)
+echo "$DIR in $COMMIT was last updated to upstream commit $rev (tree $tree_subtree)"
+
+# ... and compare the actual tree with it
if [ "$tree_actual_tree" != "$tree_subtree" ]; then
- git diff-tree $tree_actual_tree $tree_subtree >&2
- echo "FAIL: subtree directory tree doesn't match subtree commit tree" >&2
+ echo "FAIL: subtree update commit differs from upstream tree!" >&2
exit 1
fi
+
echo "GOOD"
diff --git a/contrib/devtools/lint-all.sh b/contrib/devtools/lint-all.sh
new file mode 100755
index 0000000000..b6d86959c6
--- /dev/null
+++ b/contrib/devtools/lint-all.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# Copyright (c) 2017 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#
+# This script runs all contrib/devtools/lint-*.sh files, and fails if any exit
+# with a non-zero status code.
+
+set -u
+
+SCRIPTDIR=$(dirname "${BASH_SOURCE[0]}")
+LINTALL=$(basename "${BASH_SOURCE[0]}")
+
+for f in "${SCRIPTDIR}"/lint-*.sh; do
+ if [ "$(basename "$f")" != "$LINTALL" ]; then
+ if ! "$f"; then
+ echo "^---- failure generated from $f"
+ exit 1
+ fi
+ fi
+done
diff --git a/contrib/devtools/lint-whitespace.sh b/contrib/devtools/lint-whitespace.sh
new file mode 100755
index 0000000000..989923f31a
--- /dev/null
+++ b/contrib/devtools/lint-whitespace.sh
@@ -0,0 +1,88 @@
+#!/bin/bash
+#
+# Copyright (c) 2017 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#
+# Check for new lines in diff that introduce trailing whitespace.
+
+# We can't run this check unless we know the commit range for the PR.
+if [ -z "${TRAVIS_COMMIT_RANGE}" ]; then
+ echo "Cannot run lint-whitespace.sh without commit range. To run locally, use:"
+ echo "TRAVIS_COMMIT_RANGE='<commit range>' .lint-whitespace.sh"
+ echo "For example:"
+ echo "TRAVIS_COMMIT_RANGE='47ba2c3...ee50c9e' .lint-whitespace.sh"
+ exit 1
+fi
+
+showdiff() {
+ if ! git diff -U0 "${TRAVIS_COMMIT_RANGE}" -- "." ":(exclude)src/leveldb/" ":(exclude)src/secp256k1/" ":(exclude)src/univalue/" ":(exclude)doc/release-notes/"; then
+ echo "Failed to get a diff"
+ exit 1
+ fi
+}
+
+showcodediff() {
+ if ! git diff -U0 "${TRAVIS_COMMIT_RANGE}" -- *.cpp *.h *.md *.py *.sh ":(exclude)src/leveldb/" ":(exclude)src/secp256k1/" ":(exclude)src/univalue/" ":(exclude)doc/release-notes/"; then
+ echo "Failed to get a diff"
+ exit 1
+ fi
+}
+
+RET=0
+
+# Check if trailing whitespace was found in the diff.
+if showdiff | grep -E -q '^\+.*\s+$'; then
+ echo "This diff appears to have added new lines with trailing whitespace."
+ echo "The following changes were suspected:"
+ FILENAME=""
+ SEEN=0
+ while read -r line; do
+ if [[ "$line" =~ ^diff ]]; then
+ FILENAME="$line"
+ SEEN=0
+ elif [[ "$line" =~ ^@@ ]]; then
+ LINENUMBER="$line"
+ else
+ if [ "$SEEN" -eq 0 ]; then
+ # The first time a file is seen with trailing whitespace, we print the
+ # filename (preceded by a newline).
+ echo
+ echo "$FILENAME"
+ echo "$LINENUMBER"
+ SEEN=1
+ fi
+ echo "$line"
+ fi
+ done < <(showdiff | grep -E '^(diff --git |@@|\+.*\s+$)')
+ RET=1
+fi
+
+# Check if tab characters were found in the diff.
+if showcodediff | grep -P -q '^\+.*\t'; then
+ echo "This diff appears to have added new lines with tab characters instead of spaces."
+ echo "The following changes were suspected:"
+ FILENAME=""
+ SEEN=0
+ while read -r line; do
+ if [[ "$line" =~ ^diff ]]; then
+ FILENAME="$line"
+ SEEN=0
+ elif [[ "$line" =~ ^@@ ]]; then
+ LINENUMBER="$line"
+ else
+ if [ "$SEEN" -eq 0 ]; then
+ # The first time a file is seen with a tab character, we print the
+ # filename (preceded by a newline).
+ echo
+ echo "$FILENAME"
+ echo "$LINENUMBER"
+ SEEN=1
+ fi
+ echo "$line"
+ fi
+ done < <(showcodediff | grep -P '^(diff --git |@@|\+.*\t)')
+ RET=1
+fi
+
+exit $RET