r233919 - [utils] Add Check Compile Flow Consistency tool (check_cfc.py).

Russell Gallop russell.gallop at gmail.com
Thu Apr 2 08:01:53 PDT 2015


Author: russell_gallop
Date: Thu Apr  2 10:01:53 2015
New Revision: 233919

URL: http://llvm.org/viewvc/llvm-project?rev=233919&view=rev
Log:
[utils] Add Check Compile Flow Consistency tool (check_cfc.py).

This is a tool for checking consistency of code generation with different
compiler options (such as -g or outputting to .s). This tool has found a number
of code generation issues. The script acts as a wrapper to clang or clang++
performing 2 (or more) compiles then comparing the object files. Instructions
for use are in check_cfc.py including how to use with LNT.

Differential Revision: http://reviews.llvm.org/D8723

Added:
    cfe/trunk/utils/check_cfc/
    cfe/trunk/utils/check_cfc/check_cfc.cfg
    cfe/trunk/utils/check_cfc/check_cfc.py   (with props)
    cfe/trunk/utils/check_cfc/obj_diff.py   (with props)
    cfe/trunk/utils/check_cfc/setup.py
    cfe/trunk/utils/check_cfc/test_check_cfc.py   (with props)

Added: cfe/trunk/utils/check_cfc/check_cfc.cfg
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/utils/check_cfc/check_cfc.cfg?rev=233919&view=auto
==============================================================================
--- cfe/trunk/utils/check_cfc/check_cfc.cfg (added)
+++ cfe/trunk/utils/check_cfc/check_cfc.cfg Thu Apr  2 10:01:53 2015
@@ -0,0 +1,3 @@
+[Checks]
+dash_g_no_change = true
+dash_s_no_change = true

Added: cfe/trunk/utils/check_cfc/check_cfc.py
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/utils/check_cfc/check_cfc.py?rev=233919&view=auto
==============================================================================
--- cfe/trunk/utils/check_cfc/check_cfc.py (added)
+++ cfe/trunk/utils/check_cfc/check_cfc.py Thu Apr  2 10:01:53 2015
@@ -0,0 +1,388 @@
+#!/usr/bin/env python2.7
+
+"""Check CFC - Check Compile Flow Consistency
+
+This is a compiler wrapper for testing that code generation is consistent with
+different compilation processes. It checks that code is not unduly affected by
+compiler options or other changes which should not have side effects.
+
+To use:
+-Ensure that the compiler under test (i.e. clang, clang++) is on the PATH
+-On Linux copy this script to the name of the compiler
+   e.g. cp check_cfc.py clang && cp check_cfc.py clang++
+-On Windows use setup.py to generate check_cfc.exe and copy that to clang.exe
+ and clang++.exe
+-Enable the desired checks in check_cfc.cfg (in the same directory as the
+ wrapper)
+   e.g.
+[Checks]
+dash_g_no_change = true
+dash_s_no_change = false
+
+-The wrapper can be run using its absolute path or added to PATH before the
+ compiler under test
+   e.g. export PATH=<path to check_cfc>:$PATH
+-Compile as normal. The wrapper intercepts normal -c compiles and will return
+ non-zero if the check fails.
+   e.g.
+$ clang -c test.cpp
+Code difference detected with -g
+--- /tmp/tmp5nv893.o
++++ /tmp/tmp6Vwjnc.o
+@@ -1 +1 @@
+-   0:       48 8b 05 51 0b 20 00    mov    0x200b51(%rip),%rax
++   0:       48 39 3d 51 0b 20 00    cmp    %rdi,0x200b51(%rip)
+
+-To run LNT with Check CFC specify the absolute path to the wrapper to the --cc
+ and --cxx options
+   e.g.
+   lnt runtest nt --cc <path to check_cfc>/clang \\
+           --cxx <path to check_cfc>/clang++ ...
+
+To add a new check:
+-Create a new subclass of WrapperCheck
+-Implement the perform_check() method. This should perform the alternate compile
+ and do the comparison.
+-Add the new check to check_cfc.cfg. The check has the same name as the
+ subclass.
+"""
+
+from __future__ import print_function
+
+import imp
+import os
+import platform
+import shutil
+import subprocess
+import sys
+import tempfile
+import ConfigParser
+import io
+
+import obj_diff
+
+def is_windows():
+    """Returns True if running on Windows."""
+    return platform.system() == 'Windows'
+
+class WrapperStepException(Exception):
+    """Exception type to be used when a step other than the original compile
+    fails."""
+    def __init__(self, msg, stdout, stderr):
+        self.msg = msg
+        self.stdout = stdout
+        self.stderr = stderr
+
+class WrapperCheckException(Exception):
+    """Exception type to be used when a comparison check fails."""
+    def __init__(self, msg):
+        self.msg = msg
+
+def main_is_frozen():
+    """Returns True when running as a py2exe executable."""
+    return (hasattr(sys, "frozen") or # new py2exe
+            hasattr(sys, "importers") or # old py2exe
+            imp.is_frozen("__main__")) # tools/freeze
+
+def get_main_dir():
+    """Get the directory that the script or executable is located in."""
+    if main_is_frozen():
+        return os.path.dirname(sys.executable)
+    return os.path.dirname(sys.argv[0])
+
+def remove_dir_from_path(path_var, directory):
+    """Remove the specified directory from path_var, a string representing
+    PATH"""
+    pathlist = path_var.split(os.pathsep)
+    norm_directory = os.path.normpath(os.path.normcase(directory))
+    pathlist = filter(lambda x: os.path.normpath(
+        os.path.normcase(x)) != norm_directory, pathlist)
+    return os.pathsep.join(pathlist)
+
+def path_without_wrapper():
+    """Returns the PATH variable modified to remove the path to this program."""
+    scriptdir = get_main_dir()
+    path = os.environ['PATH']
+    return remove_dir_from_path(path, scriptdir)
+
+def flip_dash_g(args):
+    """Search for -g in args. If it exists then return args without. If not then
+    add it."""
+    if '-g' in args:
+        # Return args without any -g
+        return [x for x in args if x != '-g']
+    else:
+        # No -g, add one
+        return args + ['-g']
+
+def derive_output_file(args):
+    """Derive output file from the input file (if just one) or None
+    otherwise."""
+    infile = get_input_file(args)
+    if infile is None:
+        return None
+    else:
+        return '{}.o'.format(os.path.splitext(infile)[0])
+
+def get_output_file(args):
+    """Return the output file specified by this command or None if not
+    specified."""
+    grabnext = False
+    for arg in args:
+        if grabnext:
+            return arg
+        if arg == '-o':
+            # Specified as a separate arg
+            grabnext = True
+        elif arg.startswith('-o'):
+            # Specified conjoined with -o
+            return arg[2:]
+    assert grabnext == False
+
+    return None
+
+def is_output_specified(args):
+    """Return true is output file is specified in args."""
+    return get_output_file(args) is not None
+
+def replace_output_file(args, new_name):
+    """Replaces the specified name of an output file with the specified name.
+    Assumes that the output file name is specified in the command line args."""
+    replaceidx = None
+    attached = False
+    for idx, val in enumerate(args):
+        if val == '-o':
+            replaceidx = idx + 1
+            attached = False
+        elif val.startswith('-o'):
+            replaceidx = idx
+            attached = True
+
+    if replaceidx is None:
+        raise Exception
+    replacement = new_name
+    if attached == True:
+        replacement = '-o' + new_name
+    args[replaceidx] = replacement
+    return args
+
+def add_output_file(args, output_file):
+    """Append an output file to args, presuming not already specified."""
+    return args + ['-o', output_file]
+
+def set_output_file(args, output_file):
+    """Set the output file within the arguments. Appends or replaces as
+    appropriate."""
+    if is_output_specified(args):
+        args = replace_output_file(args, output_file)
+    else:
+        args = add_output_file(args, output_file)
+    return args
+
+gSrcFileSuffixes = ('.c', '.cpp', '.cxx', '.c++', '.cp', '.cc')
+
+def get_input_file(args):
+    """Return the input file string if it can be found (and there is only
+    one)."""
+    inputFiles = list()
+    for arg in args:
+        testarg = arg
+        quotes = ('"', "'")
+        while testarg.endswith(quotes):
+            testarg = testarg[:-1]
+        testarg = os.path.normcase(testarg)
+
+        # Test if it is a source file
+        if testarg.endswith(gSrcFileSuffixes):
+            inputFiles.append(arg)
+    if len(inputFiles) == 1:
+        return inputFiles[0]
+    else:
+        return None
+
+def set_input_file(args, input_file):
+    """Replaces the input file with that specified."""
+    infile = get_input_file(args)
+    if infile:
+        infile_idx = args.index(infile)
+        args[infile_idx] = input_file
+        return args
+    else:
+        # Could not find input file
+        assert False
+
+def is_normal_compile(args):
+    """Check if this is a normal compile which will output an object file rather
+    than a preprocess or link."""
+    compile_step = '-c' in args
+    # Bitcode cannot be disassembled in the same way
+    bitcode = '-flto' in args or '-emit-llvm' in args
+    # Version and help are queries of the compiler and override -c if specified
+    query = '--version' in args or '--help' in args
+    # Check if the input is recognised as a source file (this may be too
+    # strong a restriction)
+    input_is_valid = bool(get_input_file(args))
+    return compile_step and not bitcode and not query and input_is_valid
+
+def run_step(command, my_env, error_on_failure):
+    """Runs a step of the compilation. Reports failure as exception."""
+    # Need to use shell=True on Windows as Popen won't use PATH otherwise.
+    p = subprocess.Popen(command, stdout=subprocess.PIPE,
+                         stderr=subprocess.PIPE, env=my_env, shell=is_windows())
+    (stdout, stderr) = p.communicate()
+    if p.returncode != 0:
+        raise WrapperStepException(error_on_failure, stdout, stderr)
+
+def get_temp_file_name(suffix):
+    """Get a temporary file name with a particular suffix. Let the caller be
+    reponsible for deleting it."""
+    tf = tempfile.NamedTemporaryFile(suffix=suffix, delete=False)
+    tf.close()
+    return tf.name
+
+class WrapperCheck(object):
+    """Base class for a check. Subclass this to add a check."""
+    def __init__(self, output_file_a):
+        """Record the base output file that will be compared against."""
+        self._output_file_a = output_file_a
+
+    def perform_check(self, arguments, my_env):
+        """Override this to perform the modified compilation and required
+        checks."""
+        raise NotImplementedError("Please Implement this method")
+
+class dash_g_no_change(WrapperCheck):
+    def perform_check(self, arguments, my_env):
+        """Check if different code is generated with/without the -g flag."""
+        output_file_b = get_temp_file_name('.o')
+
+        alternate_command = list(arguments)
+        alternate_command = flip_dash_g(alternate_command)
+        alternate_command = set_output_file(alternate_command, output_file_b)
+        run_step(alternate_command, my_env, "Error compiling with -g")
+
+        # Compare disassembly (returns first diff if differs)
+        difference = obj_diff.compare_object_files(self._output_file_a,
+                                                   output_file_b)
+        if difference:
+            raise WrapperCheckException(
+                "Code difference detected with -g\n{}".format(difference))
+
+        # Clean up temp file if comparison okay
+        os.remove(output_file_b)
+
+class dash_s_no_change(WrapperCheck):
+    def perform_check(self, arguments, my_env):
+        """Check if compiling to asm then assembling in separate steps results
+        in different code than compiling to object directly."""
+        output_file_b = get_temp_file_name('.o')
+
+        alternate_command = arguments + ['-via-file-asm']
+        alternate_command = set_output_file(alternate_command, output_file_b)
+        run_step(alternate_command, my_env,
+                 "Error compiling with -via-file-asm")
+
+        # Compare disassembly (returns first diff if differs)
+        difference = obj_diff.compare_object_files(self._output_file_a,
+                                                   output_file_b)
+        if difference:
+            raise WrapperCheckException(
+                "Code difference detected with -S\n{}".format(difference))
+
+        # Clean up temp file if comparison okay
+        os.remove(output_file_b)
+
+if __name__ == '__main__':
+    # Create configuration defaults from list of checks
+    default_config = """
+[Checks]
+"""
+
+    # Find all subclasses of WrapperCheck
+    checks = [cls.__name__ for cls in vars()['WrapperCheck'].__subclasses__()]
+
+    for c in checks:
+        default_config += "{} = false\n".format(c)
+
+    config = ConfigParser.RawConfigParser()
+    config.readfp(io.BytesIO(default_config))
+    scriptdir = get_main_dir()
+    config_path = os.path.join(scriptdir, 'check_cfc.cfg')
+    try:
+        config.read(os.path.join(config_path))
+    except:
+        print("Could not read config from {}, "
+              "using defaults.".format(config_path))
+
+    my_env = os.environ.copy()
+    my_env['PATH'] = path_without_wrapper()
+
+    arguments_a = list(sys.argv)
+
+    # Prevent infinite loop if called with absolute path.
+    arguments_a[0] = os.path.basename(arguments_a[0])
+
+    # Sanity check
+    enabled_checks = [check_name
+                      for check_name in checks
+                      if config.getboolean('Checks', check_name)]
+    checks_comma_separated = ', '.join(enabled_checks)
+    print("Check CFC, checking: {}".format(checks_comma_separated))
+
+    # A - original compilation
+    output_file_orig = get_output_file(arguments_a)
+    if output_file_orig is None:
+        output_file_orig = derive_output_file(arguments_a)
+
+    p = subprocess.Popen(arguments_a, env=my_env, shell=is_windows())
+    p.communicate()
+    if p.returncode != 0:
+        sys.exit(p.returncode)
+
+    if not is_normal_compile(arguments_a) or output_file_orig is None:
+        # Bail out here if we can't apply checks in this case.
+        # Does not indicate an error.
+        # Maybe not straight compilation (e.g. -S or --version or -flto)
+        # or maybe > 1 input files.
+        sys.exit(0)
+
+    # Sometimes we generate files which have very long names which can't be
+    # read/disassembled. This will exit early if we can't find the file we
+    # expected to be output.
+    if not os.path.isfile(output_file_orig):
+        sys.exit(0)
+
+    # Copy output file to a temp file
+    temp_output_file_orig = get_temp_file_name('.o')
+    shutil.copyfile(output_file_orig, temp_output_file_orig)
+
+    # Run checks, if they are enabled in config and if they are appropriate for
+    # this command line.
+    current_module = sys.modules[__name__]
+    for check_name in checks:
+        if config.getboolean('Checks', check_name):
+            class_ = getattr(current_module, check_name)
+            checker = class_(temp_output_file_orig)
+            try:
+                checker.perform_check(arguments_a, my_env)
+            except WrapperCheckException as e:
+                # Check failure
+                print(e.msg, file=sys.stderr)
+
+                # Remove file to comply with build system expectations (no
+                # output file if failed)
+                os.remove(output_file_orig)
+                sys.exit(1)
+
+            except WrapperStepException as e:
+                # Compile step failure
+                print(e.msg, file=sys.stderr)
+                print("*** stdout ***", file=sys.stderr)
+                print(e.stdout, file=sys.stderr)
+                print("*** stderr ***", file=sys.stderr)
+                print(e.stderr, file=sys.stderr)
+
+                # Remove file to comply with build system expectations (no
+                # output file if failed)
+                os.remove(output_file_orig)
+                sys.exit(1)

Propchange: cfe/trunk/utils/check_cfc/check_cfc.py
------------------------------------------------------------------------------
    svn:executable = *

Added: cfe/trunk/utils/check_cfc/obj_diff.py
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/utils/check_cfc/obj_diff.py?rev=233919&view=auto
==============================================================================
--- cfe/trunk/utils/check_cfc/obj_diff.py (added)
+++ cfe/trunk/utils/check_cfc/obj_diff.py Thu Apr  2 10:01:53 2015
@@ -0,0 +1,79 @@
+#!/usr/bin/env python2.7
+
+from __future__ import print_function
+
+import argparse
+import difflib
+import os
+import subprocess
+import sys
+
+disassembler = 'objdump'
+
+def keep_line(line):
+    """Returns true for lines that should be compared in the disassembly
+    output."""
+    return "file format" not in line
+
+def disassemble(objfile):
+    """Disassemble object to a file."""
+    p = subprocess.Popen([disassembler, '-d', objfile],
+                         stdout=subprocess.PIPE,
+                         stderr=subprocess.PIPE)
+    (out, err) = p.communicate()
+    if p.returncode or err:
+        print("Disassemble failed: {}".format(objfile))
+        sys.exit(1)
+    return filter(keep_line, out.split(os.linesep))
+
+def first_diff(a, b, fromfile, tofile):
+    """Returns the first few lines of a difference, if there is one.  Python
+    diff can be very slow with large objects and the most interesting changes
+    are the first ones. Truncate data before sending to difflib.  Returns None
+    is there is no difference."""
+
+    # Find first diff
+    first_diff_idx = None
+    for idx, val in enumerate(a):
+        if val != b[idx]:
+            first_diff_idx = idx
+            break
+
+    if first_diff_idx == None:
+        # No difference
+        return None
+
+    # Diff to first line of diff plus some lines
+    context = 3
+    diff = difflib.unified_diff(a[:first_diff_idx+context],
+                                b[:first_diff_idx+context],
+                                fromfile,
+                                tofile)
+    difference = "\n".join(diff)
+    if first_diff_idx + context < len(a):
+        difference += "\n*** Diff truncated ***"
+    return difference
+
+def compare_object_files(objfilea, objfileb):
+    """Compare disassembly of two different files.
+       Allowing unavoidable differences, such as filenames.
+       Return the first difference if the disassembly differs, or None.
+    """
+    disa = disassemble(objfilea)
+    disb = disassemble(objfileb)
+    return first_diff(disa, disb, objfilea, objfileb)
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser()
+    parser.add_argument('objfilea', nargs=1)
+    parser.add_argument('objfileb', nargs=1)
+    parser.add_argument('-v', '--verbose', action='store_true')
+    args = parser.parse_args()
+    diff = compare_object_files(args.objfilea[0], args.objfileb[0])
+    if diff:
+        print("Difference detected")
+        if args.verbose:
+            print(diff)
+        sys.exit(1)
+    else:
+        print("The same")

Propchange: cfe/trunk/utils/check_cfc/obj_diff.py
------------------------------------------------------------------------------
    svn:executable = *

Added: cfe/trunk/utils/check_cfc/setup.py
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/utils/check_cfc/setup.py?rev=233919&view=auto
==============================================================================
--- cfe/trunk/utils/check_cfc/setup.py (added)
+++ cfe/trunk/utils/check_cfc/setup.py Thu Apr  2 10:01:53 2015
@@ -0,0 +1,21 @@
+"""For use on Windows. Run with:
+    python.exe setup.py py2exe
+    """
+from distutils.core import setup
+try:
+    import py2exe
+except ImportError:
+    import platform
+    import sys
+    if platform.system() == 'Windows':
+        print "Could not find py2exe. Please install then run setup.py py2exe."
+        raise
+    else:
+        print "setup.py only required on Windows."
+        sys.exit(1)
+
+setup(
+      console=['check_cfc.py'],
+      name="Check CFC",
+      description='Check Compile Flow Consistency'
+      )

Added: cfe/trunk/utils/check_cfc/test_check_cfc.py
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/utils/check_cfc/test_check_cfc.py?rev=233919&view=auto
==============================================================================
--- cfe/trunk/utils/check_cfc/test_check_cfc.py (added)
+++ cfe/trunk/utils/check_cfc/test_check_cfc.py Thu Apr  2 10:01:53 2015
@@ -0,0 +1,158 @@
+#!/usr/bin/env python2.7
+
+"""Test internal functions within check_cfc.py."""
+
+import check_cfc
+import os
+import platform
+import unittest
+
+
+class TestCheckCFC(unittest.TestCase):
+
+    def test_flip_dash_g(self):
+        self.assertIn('-g', check_cfc.flip_dash_g(['clang', '-c']))
+        self.assertNotIn('-g', check_cfc.flip_dash_g(['clang', '-c', '-g']))
+        self.assertNotIn(
+            '-g', check_cfc.flip_dash_g(['clang', '-g', '-c', '-g']))
+
+    def test_remove_dir_from_path(self):
+        bin_path = r'/usr/bin'
+        space_path = r'/home/user/space in path'
+        superstring_path = r'/usr/bin/local'
+
+        # Test removing last thing in path
+        self.assertNotIn(
+            bin_path, check_cfc.remove_dir_from_path(bin_path, bin_path))
+
+        # Test removing one entry and leaving others
+        # Also tests removing repeated path
+        path_var = os.pathsep.join(
+            [superstring_path, bin_path, space_path, bin_path])
+        stripped_path_var = check_cfc.remove_dir_from_path(path_var, bin_path)
+        self.assertIn(superstring_path, stripped_path_var)
+        self.assertNotIn(bin_path, stripped_path_var.split(os.pathsep))
+        self.assertIn(space_path, stripped_path_var)
+
+        # Test removing non-canonical path
+        self.assertNotIn(r'/usr//bin',
+                         check_cfc.remove_dir_from_path(r'/usr//bin', bin_path))
+
+        if platform == 'Windows':
+            # Windows is case insensitive so should remove a different case
+            # path
+            self.assertNotIn(
+                bin_path, check_cfc.remove_dir_from_path(path_var, r'/USR/BIN'))
+        else:
+            # Case sensitive so will not remove different case path
+            self.assertIn(
+                bin_path, check_cfc.remove_dir_from_path(path_var, r'/USR/BIN'))
+
+    def test_is_output_specified(self):
+        self.assertTrue(
+            check_cfc.is_output_specified(['clang', '-o', 'test.o']))
+        self.assertTrue(check_cfc.is_output_specified(['clang', '-otest.o']))
+        self.assertFalse(
+            check_cfc.is_output_specified(['clang', '-gline-tables-only']))
+        # Not specified for implied output file name
+        self.assertFalse(check_cfc.is_output_specified(['clang', 'test.c']))
+
+    def test_get_output_file(self):
+        self.assertEqual(
+            check_cfc.get_output_file(['clang', '-o', 'test.o']), 'test.o')
+        self.assertEqual(
+            check_cfc.get_output_file(['clang', '-otest.o']), 'test.o')
+        self.assertIsNone(
+            check_cfc.get_output_file(['clang', '-gline-tables-only']))
+        # Can't get output file if more than one input file
+        self.assertIsNone(
+            check_cfc.get_output_file(['clang', '-c', 'test.cpp', 'test2.cpp']))
+        # No output file specified
+        self.assertIsNone(check_cfc.get_output_file(['clang', '-c', 'test.c']))
+
+    def test_derive_output_file(self):
+        # Test getting implicit output file
+        self.assertEqual(
+            check_cfc.derive_output_file(['clang', '-c', 'test.c']), 'test.o')
+        self.assertEqual(
+            check_cfc.derive_output_file(['clang', '-c', 'test.cpp']), 'test.o')
+        self.assertIsNone(check_cfc.derive_output_file(['clang', '--version']))
+
+    def test_is_normal_compile(self):
+        self.assertTrue(check_cfc.is_normal_compile(
+            ['clang', '-c', 'test.cpp', '-o', 'test2.o']))
+        self.assertTrue(
+            check_cfc.is_normal_compile(['clang', '-c', 'test.cpp']))
+        # Outputting bitcode is not a normal compile
+        self.assertFalse(
+            check_cfc.is_normal_compile(['clang', '-c', 'test.cpp', '-flto']))
+        self.assertFalse(
+            check_cfc.is_normal_compile(['clang', '-c', 'test.cpp', '-emit-llvm']))
+        # Outputting preprocessed output or assembly is not a normal compile
+        self.assertFalse(
+            check_cfc.is_normal_compile(['clang', '-E', 'test.cpp', '-o', 'test.ii']))
+        self.assertFalse(
+            check_cfc.is_normal_compile(['clang', '-S', 'test.cpp', '-o', 'test.s']))
+        # Input of preprocessed or assembly is not a "normal compile"
+        self.assertFalse(
+            check_cfc.is_normal_compile(['clang', '-c', 'test.s', '-o', 'test.o']))
+        self.assertFalse(
+            check_cfc.is_normal_compile(['clang', '-c', 'test.ii', '-o', 'test.o']))
+        # Specifying --version and -c is not a normal compile
+        self.assertFalse(
+            check_cfc.is_normal_compile(['clang', '-c', 'test.cpp', '--version']))
+        self.assertFalse(
+            check_cfc.is_normal_compile(['clang', '-c', 'test.cpp', '--help']))
+
+    def test_replace_output_file(self):
+        self.assertEqual(check_cfc.replace_output_file(
+            ['clang', '-o', 'test.o'], 'testg.o'), ['clang', '-o', 'testg.o'])
+        self.assertEqual(check_cfc.replace_output_file(
+            ['clang', '-otest.o'], 'testg.o'), ['clang', '-otestg.o'])
+        with self.assertRaises(Exception):
+            check_cfc.replace_output_file(['clang'], 'testg.o')
+
+    def test_add_output_file(self):
+        self.assertEqual(check_cfc.add_output_file(
+            ['clang'], 'testg.o'), ['clang', '-o', 'testg.o'])
+
+    def test_set_output_file(self):
+        # Test output not specified
+        self.assertEqual(
+            check_cfc.set_output_file(['clang'], 'test.o'), ['clang', '-o', 'test.o'])
+        # Test output is specified
+        self.assertEqual(check_cfc.set_output_file(
+            ['clang', '-o', 'test.o'], 'testb.o'), ['clang', '-o', 'testb.o'])
+
+    def test_get_input_file(self):
+        # No input file
+        self.assertIsNone(check_cfc.get_input_file(['clang']))
+        # Input C file
+        self.assertEqual(
+            check_cfc.get_input_file(['clang', 'test.c']), 'test.c')
+        # Input C++ file
+        self.assertEqual(
+            check_cfc.get_input_file(['clang', 'test.cpp']), 'test.cpp')
+        # Multiple input files
+        self.assertIsNone(
+            check_cfc.get_input_file(['clang', 'test.c', 'test2.cpp']))
+        self.assertIsNone(
+            check_cfc.get_input_file(['clang', 'test.c', 'test2.c']))
+        # Don't handle preprocessed files
+        self.assertIsNone(check_cfc.get_input_file(['clang', 'test.i']))
+        self.assertIsNone(check_cfc.get_input_file(['clang', 'test.ii']))
+        # Test identifying input file with quotes
+        self.assertEqual(
+            check_cfc.get_input_file(['clang', '"test.c"']), '"test.c"')
+        self.assertEqual(
+            check_cfc.get_input_file(['clang', "'test.c'"]), "'test.c'")
+        # Test multiple quotes
+        self.assertEqual(
+            check_cfc.get_input_file(['clang', "\"'test.c'\""]), "\"'test.c'\"")
+
+    def test_set_input_file(self):
+        self.assertEqual(check_cfc.set_input_file(
+            ['clang', 'test.c'], 'test.s'), ['clang', 'test.s'])
+
+if __name__ == '__main__':
+    unittest.main()

Propchange: cfe/trunk/utils/check_cfc/test_check_cfc.py
------------------------------------------------------------------------------
    svn:executable = *





More information about the cfe-commits mailing list