From owner-svn-src-user@FreeBSD.ORG Sun Oct 14 22:28:57 2012 Return-Path: Delivered-To: svn-src-user@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [69.147.83.52]) by hub.freebsd.org (Postfix) with ESMTP id 3C067790; Sun, 14 Oct 2012 22:28:57 +0000 (UTC) (envelope-from crees@FreeBSD.org) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:4f8:fff6::2c]) by mx1.freebsd.org (Postfix) with ESMTP id 240878FC12; Sun, 14 Oct 2012 22:28:57 +0000 (UTC) Received: from svn.freebsd.org (localhost [127.0.0.1]) by svn.freebsd.org (8.14.4/8.14.4) with ESMTP id q9EMSu0B055102; Sun, 14 Oct 2012 22:28:56 GMT (envelope-from crees@svn.freebsd.org) Received: (from crees@localhost) by svn.freebsd.org (8.14.4/8.14.4/Submit) id q9EMSuvW055100; Sun, 14 Oct 2012 22:28:56 GMT (envelope-from crees@svn.freebsd.org) Message-Id: <201210142228.q9EMSuvW055100@svn.freebsd.org> From: Chris Rees Date: Sun, 14 Oct 2012 22:28:56 +0000 (UTC) To: src-committers@freebsd.org, svn-src-user@freebsd.org Subject: svn commit: r241560 - in user/crees: . rclint X-SVN-Group: user MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-user@freebsd.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "SVN commit messages for the experimental " user" src tree" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sun, 14 Oct 2012 22:28:57 -0000 Author: crees (ports committer) Date: Sun Oct 14 22:28:56 2012 New Revision: 241560 URL: http://svn.freebsd.org/changeset/base/241560 Log: Add the framework for an RC script testing tool. It will mostly be used by ports developers, but a base mode is possible Added: user/crees/ user/crees/rclint/ user/crees/rclint/errors.en user/crees/rclint/rclint.py (contents, props changed) Added: user/crees/rclint/errors.en ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ user/crees/rclint/errors.en Sun Oct 14 22:28:56 2012 (r241560) @@ -0,0 +1,15 @@ +# Rigid format of this file; colon-separated tuples to define +# error messages and explanations. +# format as follows: +error_id:message:explanation +name_missing:name is set late or not at all:Directly following the sourcing of scripts must follow setting of the variable name. +name_quoted:name is quoted:Do not quote the value of name. If it has spaces, use underscores. +rc_subr_late:rc.subr sourcing late or nonexistent:The first non-comment non-blank line in any rc file must be sourcing /etc/rc.subr. +rcorder_keyword_freebsd:Do not include FreeBSD in the KEYWORD rcorder line:Historically FreeBSD scripts were marked in the KEYWORD section. This is no longer necessary. +rcorder_missing:Missing rcorder block:Following the FreeBSD RCSId keyword must be nothing but empty comment lines and an rcorder block. +rcorder_order:rcorder block in the wrong order; should be PROVIDE/REQUIRE/BEFORE/KEYWORD:See the article on RC scripting. +rcvar_incorrect:rcvar is not set correctly:rcvar must be directly set to name_enable. Do not quote, and do not use indirection; ${name}_enable is slower than example_enable. +rcvar_missing:rcvar is set late or not at all:Setting rcvar must be done straight after setting name. +rcvar_quoted:rcvar is quoted:Do not quote the value of rcvar. +rcsid:Missing FreeBSD RCSId keyword:All rc scripts must contain a line beginning # $FreeBSD$. Do not include blank lines without comment markers at the beginning (#) until the script begins. +shebang:Incorrect shebang used:All rc scripts must start with the correct shebang; #!/bin/sh. Added: user/crees/rclint/rclint.py ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ user/crees/rclint/rclint.py Sun Oct 14 22:28:56 2012 (r241560) @@ -0,0 +1,161 @@ +#!/usr/bin/env python +# +# Copyright (c) 2012 The FreeBSD Project. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE PROJECT ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE PROJECT BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# $FreeBSD$ +# + +major = 0 +minor = 0 +micro = 0 + +import argparse +import logging +import re + +def error_explain(error): + if verbosity > 0: print error['explanation'] + +def error(type, line_number, filename): + logging.error("[%d]%s: %s " % (line_number, filename, errors[type]['message'])) + error_explain(errors[type]) + +def check_quoted(string): + return True if string[0] == '"' or string[0] == "'" else False + +def get_value(var, line): + n = re.match('%s=(\S*)$' % var, line) + if n: + value=n.group(1) + return value + else: + return False + +def do_rclint(filename): + logging.debug('Suck in file %s' % filename) + lines=[line.rstrip('\n') for line in open(filename)] + num = 0 + + # Basic order; shebang, copyright, RCSId, gap, rcorder + + logging.debug('Check shebang') + if lines[num] != '#!/bin/sh': + error('shebang', num, filename) + + logging.debug('Skipping license') + num += 1 + while (not re.match('# \$FreeBSD[:$]', lines[num]) and + (lines[num] == '' or lines[num][0] == '#')): + num += 1 + + logging.debug('Checking for RCSId') + if not re.match('# \$FreeBSD[:$]', lines[num]): + error('rcsid', num, filename) + + num += 1 + while lines[num] == '#' or lines[num] == '': num += 1 + + logging.debug('Checking rcorder order and sucking in names') + if not re.match('# [PRBK]', lines[num]): + error('rcorder_missing', num, filename) + orders = ['provide', 'require', 'before', 'keyword'] + index = 0 + rcorder = { o: [] for o in orders } + while index < 4: + order = orders[index] + try: + for result in re.match('# %s: (.*)' % order.upper(), + lines[num]).group(1).split(' '): + rcorder[order].append(result) + num += 1 + except: + index += 1 + + if 'FreeBSD' in rcorder['keyword']: + error('rcorder_keyword_freebsd', num, filename) + + if re.match('# [PRBK]', lines[num]): + error('rcorder_order', num, filename) + + documentation_line = num + + logging.debug('Checking sourcing lines') + while lines[num] == '' or lines[num][0] == '#': + num += 1 + + if lines[num] != '. /etc/rc.subr': + error('rc_subr_late', num, filename) + + logging.debug('Checking name assignment') + while lines[num] == '' or lines[num][0] == '#' or lines[num][0] == '.': + num += 1 + + name = get_value('name', lines[num]) + if not name: + error('name_missing', num, filename) + elif check_quoted(name): + error('name_quoted', num, filename) + else: + logging.debug('name discovered as %s' % name) + num += 1 + + logging.debug('Checking rcvar') + rcvar = get_value('rcvar', lines[num]) + logging.debug('rcvar discovered as %s' % rcvar if rcvar else 'rcvar not discovered') + if not rcvar: + error('rcvar_missing', num, filename) + elif check_quoted(rcvar): + error('rcvar_quoted', num, filename) + elif rcvar != '%s_enable' % name: + error('rcvar_incorrect', num, filename) + else: + num += 1 + + +parser = argparse.ArgumentParser() +parser.add_argument('filenames', nargs = '+') +parser.add_argument('--language', nargs = 1, type=str, default = ['en'], help = 'sets the language that errors are reported in') +parser.add_argument('-v', action='count', help='raises debug level; provides detailed explanations of errors') + +args = parser.parse_args() + +verbosity = args.v +errordb = 'errors.%s' % args.language[0] + +logging.basicConfig(level=(logging.DEBUG if verbosity > 1 else logging.ERROR)) + +try: + with open(errordb) as f: + logging.debug('Sucking in error database') + errors = {} + for e in f.readlines(): + if e[0] == '#' or len(e) == 1: + continue + e = e.split(':') + errors[e[0]] = { 'message': e[1], 'explanation': e[2] } +except: + logging.error('Cannot open database for language %s' % errordb) + exit() + +for file in args.filenames: + do_rclint(file)