1#!/usr/bin/env python
2
3"""Test internal functions within check_cfc.py."""
4
5import check_cfc
6import os
7import platform
8import unittest
9
10
11class TestCheckCFC(unittest.TestCase):
12
13    def test_flip_dash_g(self):
14        self.assertIn('-g', check_cfc.flip_dash_g(['clang', '-c']))
15        self.assertNotIn('-g', check_cfc.flip_dash_g(['clang', '-c', '-g']))
16        self.assertNotIn(
17            '-g', check_cfc.flip_dash_g(['clang', '-g', '-c', '-g']))
18
19    def test_remove_dir_from_path(self):
20        bin_path = r'/usr/bin'
21        space_path = r'/home/user/space in path'
22        superstring_path = r'/usr/bin/local'
23
24        # Test removing last thing in path
25        self.assertNotIn(
26            bin_path, check_cfc.remove_dir_from_path(bin_path, bin_path))
27
28        # Test removing one entry and leaving others
29        # Also tests removing repeated path
30        path_var = os.pathsep.join(
31            [superstring_path, bin_path, space_path, bin_path])
32        stripped_path_var = check_cfc.remove_dir_from_path(path_var, bin_path)
33        self.assertIn(superstring_path, stripped_path_var)
34        self.assertNotIn(bin_path, stripped_path_var.split(os.pathsep))
35        self.assertIn(space_path, stripped_path_var)
36
37        # Test removing non-canonical path
38        self.assertNotIn(r'/usr//bin',
39                         check_cfc.remove_dir_from_path(r'/usr//bin', bin_path))
40
41        if platform == 'Windows':
42            # Windows is case insensitive so should remove a different case
43            # path
44            self.assertNotIn(
45                bin_path, check_cfc.remove_dir_from_path(path_var, r'/USR/BIN'))
46        else:
47            # Case sensitive so will not remove different case path
48            self.assertIn(
49                bin_path, check_cfc.remove_dir_from_path(path_var, r'/USR/BIN'))
50
51    def test_is_output_specified(self):
52        self.assertTrue(
53            check_cfc.is_output_specified(['clang', '-o', 'test.o']))
54        self.assertTrue(check_cfc.is_output_specified(['clang', '-otest.o']))
55        self.assertFalse(
56            check_cfc.is_output_specified(['clang', '-gline-tables-only']))
57        # Not specified for implied output file name
58        self.assertFalse(check_cfc.is_output_specified(['clang', 'test.c']))
59
60    def test_get_output_file(self):
61        self.assertEqual(
62            check_cfc.get_output_file(['clang', '-o', 'test.o']), 'test.o')
63        self.assertEqual(
64            check_cfc.get_output_file(['clang', '-otest.o']), 'test.o')
65        self.assertIsNone(
66            check_cfc.get_output_file(['clang', '-gline-tables-only']))
67        # Can't get output file if more than one input file
68        self.assertIsNone(
69            check_cfc.get_output_file(['clang', '-c', 'test.cpp', 'test2.cpp']))
70        # No output file specified
71        self.assertIsNone(check_cfc.get_output_file(['clang', '-c', 'test.c']))
72
73    def test_derive_output_file(self):
74        # Test getting implicit output file
75        self.assertEqual(
76            check_cfc.derive_output_file(['clang', '-c', 'test.c']), 'test.o')
77        self.assertEqual(
78            check_cfc.derive_output_file(['clang', '-c', 'test.cpp']), 'test.o')
79        self.assertIsNone(check_cfc.derive_output_file(['clang', '--version']))
80
81    def test_is_normal_compile(self):
82        self.assertTrue(check_cfc.is_normal_compile(
83            ['clang', '-c', 'test.cpp', '-o', 'test2.o']))
84        self.assertTrue(
85            check_cfc.is_normal_compile(['clang', '-c', 'test.cpp']))
86        # Outputting bitcode is not a normal compile
87        self.assertFalse(
88            check_cfc.is_normal_compile(['clang', '-c', 'test.cpp', '-flto']))
89        self.assertFalse(
90            check_cfc.is_normal_compile(['clang', '-c', 'test.cpp', '-emit-llvm']))
91        # Outputting preprocessed output or assembly is not a normal compile
92        self.assertFalse(
93            check_cfc.is_normal_compile(['clang', '-E', 'test.cpp', '-o', 'test.ii']))
94        self.assertFalse(
95            check_cfc.is_normal_compile(['clang', '-S', 'test.cpp', '-o', 'test.s']))
96        # Input of preprocessed or assembly is not a "normal compile"
97        self.assertFalse(
98            check_cfc.is_normal_compile(['clang', '-c', 'test.s', '-o', 'test.o']))
99        self.assertFalse(
100            check_cfc.is_normal_compile(['clang', '-c', 'test.ii', '-o', 'test.o']))
101        # Specifying --version and -c is not a normal compile
102        self.assertFalse(
103            check_cfc.is_normal_compile(['clang', '-c', 'test.cpp', '--version']))
104        self.assertFalse(
105            check_cfc.is_normal_compile(['clang', '-c', 'test.cpp', '--help']))
106        # Outputting dependency files is not a normal compile
107        self.assertFalse(
108            check_cfc.is_normal_compile(['clang', '-c', '-M', 'test.cpp']))
109        self.assertFalse(
110            check_cfc.is_normal_compile(['clang', '-c', '-MM', 'test.cpp']))
111        # Creating a dependency file as a side effect still outputs an object file
112        self.assertTrue(
113            check_cfc.is_normal_compile(['clang', '-c', '-MD', 'test.cpp']))
114        self.assertTrue(
115            check_cfc.is_normal_compile(['clang', '-c', '-MMD', 'test.cpp']))
116
117    def test_replace_output_file(self):
118        self.assertEqual(check_cfc.replace_output_file(
119            ['clang', '-o', 'test.o'], 'testg.o'), ['clang', '-o', 'testg.o'])
120        self.assertEqual(check_cfc.replace_output_file(
121            ['clang', '-otest.o'], 'testg.o'), ['clang', '-otestg.o'])
122        with self.assertRaises(Exception):
123            check_cfc.replace_output_file(['clang'], 'testg.o')
124
125    def test_add_output_file(self):
126        self.assertEqual(check_cfc.add_output_file(
127            ['clang'], 'testg.o'), ['clang', '-o', 'testg.o'])
128
129    def test_set_output_file(self):
130        # Test output not specified
131        self.assertEqual(
132            check_cfc.set_output_file(['clang'], 'test.o'), ['clang', '-o', 'test.o'])
133        # Test output is specified
134        self.assertEqual(check_cfc.set_output_file(
135            ['clang', '-o', 'test.o'], 'testb.o'), ['clang', '-o', 'testb.o'])
136
137    def test_get_input_file(self):
138        # No input file
139        self.assertIsNone(check_cfc.get_input_file(['clang']))
140        # Input C file
141        self.assertEqual(
142            check_cfc.get_input_file(['clang', 'test.c']), 'test.c')
143        # Input C++ file
144        self.assertEqual(
145            check_cfc.get_input_file(['clang', 'test.cpp']), 'test.cpp')
146        # Multiple input files
147        self.assertIsNone(
148            check_cfc.get_input_file(['clang', 'test.c', 'test2.cpp']))
149        self.assertIsNone(
150            check_cfc.get_input_file(['clang', 'test.c', 'test2.c']))
151        # Don't handle preprocessed files
152        self.assertIsNone(check_cfc.get_input_file(['clang', 'test.i']))
153        self.assertIsNone(check_cfc.get_input_file(['clang', 'test.ii']))
154        # Test identifying input file with quotes
155        self.assertEqual(
156            check_cfc.get_input_file(['clang', '"test.c"']), '"test.c"')
157        self.assertEqual(
158            check_cfc.get_input_file(['clang', "'test.c'"]), "'test.c'")
159        # Test multiple quotes
160        self.assertEqual(
161            check_cfc.get_input_file(['clang', "\"'test.c'\""]), "\"'test.c'\"")
162
163    def test_set_input_file(self):
164        self.assertEqual(check_cfc.set_input_file(
165            ['clang', 'test.c'], 'test.s'), ['clang', 'test.s'])
166
167if __name__ == '__main__':
168    unittest.main()
169