Commit 6f0e697e authored by Nick Mathewson's avatar Nick Mathewson 🐚
Browse files

Use Doctests to test the behavior of annotate_ifdef_directives.

parent 195aa2f5
Loading
Loading
Loading
Loading
+94 −21
Original line number Diff line number Diff line
@@ -2,24 +2,60 @@
# Copyright (c) 2017-2019, The Tor Project, Inc.
# See LICENSE for licensing information

# This script iterates over a list of C files. For each file, it looks at the
# #if/#else C macros, and annotates them with comments explaining what they
# match.
#
# For example, it replaces this:
#
#  #ifdef HAVE_OCELOT
#   // 500 lines of ocelot code
#  #endif
#
# with this:
#
#  #ifdef HAVE_OCELOT
#   // 500 lines of ocelot code
#  #endif /* defined(HAVE_OCELOT) */
#
# Note that only #else and #endif lines are annotated.  Existing comments
# on those lines are removed.
r"""
This script iterates over a list of C files. For each file, it looks at the
#if/#else C macros, and annotates them with comments explaining what they
match.

For example, it replaces this kind of input...

>>> INPUT = '''
... #ifdef HAVE_OCELOT
...   C code here
... #if MIMSY == BOROGROVE
...   block 1
...   block 1
...   block 1
...   block 1
... #else
...   block 2
...   block 2
...   block 2
...   block 2
... #endif
... #endif
... '''

With this kind of output:
>>> EXPECTED_OUTPUT = '''
... #ifdef HAVE_OCELOT
...   C code here
... #if MIMSY == BOROGROVE
...   block 1
...   block 1
...   block 1
...   block 1
... #else /* !(MIMSY == BOROGROVE) */
...   block 2
...   block 2
...   block 2
...   block 2
... #endif /* MIMSY == BOROGROVE */
... #endif /* defined(HAVE_OCELOT) */
... '''

Here's how to use it:
>>> import sys
>>> if sys.version_info.major < 3: from cStringIO import StringIO
>>> if sys.version_info.major >= 3: from io import StringIO

>>> OUTPUT = StringIO()
>>> translate(StringIO(INPUT), OUTPUT)
>>> assert OUTPUT.getvalue() == EXPECTED_OUTPUT

Note that only #else and #endif lines are annotated.  Existing comments
on those lines are removed.
"""

import re

@@ -38,6 +74,17 @@ class Problem(Exception):
def close_parens_needed(expr):
    """Return the number of left-parentheses needed to make 'expr'
       balanced.

    >>> close_parens_needed("1+2")
    0
    >>> close_parens_needed("(1 + 2)")
    0
    >>> close_parens_needed("(1 + 2")
    1
    >>> close_parens_needed("(1 + (2 *")
    2
    >>> close_parens_needed("(1 + (2 * 3) + (4")
    2
    """
    return expr.count("(") - expr.count(")")

@@ -47,6 +94,17 @@ def truncate_expression(expr, new_width):
       characters long.

       Try to return an expression with balanced parentheses.

    >>> truncate_expression("1+2+3", 8)
    '1+2+3'
    >>> truncate_expression("1+2+3+4+5", 8)
    '1+2+3...'
    >>> truncate_expression("(1+2+3+4)", 8)
    '(1+2...)'
    >>> truncate_expression("(1+(2+3+4))", 8)
    '(1+...)'
    >>> truncate_expression("(((((((((", 8)
    '((...))'
    """
    if len(expr) <= new_width:
        # The expression is already short enough.
@@ -69,14 +127,23 @@ def truncate_expression(expr, new_width):
    return ellipsis

def commented_line(fmt, argument, maxwidth=LINE_WIDTH):

    """
    # (This is a raw docstring so that our doctests can use \.)
    r"""
    Return fmt%argument, for use as a commented line.  If the line would
    be longer than maxwidth, truncate argument.
    be longer than maxwidth, truncate argument but try to keep its
    parentheses balanced.

    Requires that fmt%"..." will fit into maxwidth characters.

    Requires that fmt ends with a newline.

    >>> commented_line("/* %s */\n", "hello world", 32)
    '/* hello world */\n'
    >>> commented_line("/* %s */\n", "hello world", 15)
    '/* hello... */\n'
    >>> commented_line("#endif /* %s */\n", "((1+2) && defined(FOO))", 32)
    '#endif /* ((1+2) && defi...) */\n'

    """
    assert fmt.endswith("\n")
    result = fmt % argument
@@ -208,6 +275,12 @@ def translate(f_in, f_out):
        raise Problem("Missing #endif")

import sys,os

if sys.argv[1] == "--self-test":
    import doctest
    doctest.testmod()
    sys.exit(0)

for fn in sys.argv[1:]:
    with open(fn+"_OUT", 'w') as output_file:
        translate(open(fn, 'r'), output_file)