* support/regression/generate-cases.py: display warning if function list is empty
[fw/sdcc] / support / regression / generate-cases.py
1 from HTMLgen import TemplateDocument
2 import sys, re, tempfile, os
3
4 """See InstanceGenerator for a description of this file"""
5
6 # Globals
7 # Directory that the generated files should be placed into
8 outdir = sys.argv[2]
9
10 # Start of the test function table definition
11 testfuntableheader = """
12 void
13 __runSuite(void)
14 {
15 """
16
17 # End of the test function table definition
18 testfuntablefooter = """}
19 """
20
21 # Code to generate the suite function
22 testfunsuite = """
23 code const char *
24 __getSuiteName(void)
25 {
26   return "{testcase}";
27 }
28 """ 
29
30 # Utility functions
31 def trim(a):
32     """Removes all white space from the start and the end of a string.
33     Like java.lang.String.trim"""
34     ret = chomp(re.sub(r'^\s+', '', a))
35     return ret
36     
37 def chomp(a):
38     """Removes all white space from the end of a string.
39     Like perl's chomp"""
40     return re.sub(r'\s+$', '', a)
41
42 def createdir(path):
43     """Creates a directory if it doesn't exist"""
44     if not os.path.isdir(path):
45         os.mkdir(path)
46
47 class InstanceGenerator:
48     """Test case iteration generator.
49     Takes the template given as the first argument, pulls out all the meta
50     iteration information, and generates an instance for each combination
51     of the names and types.
52
53     See doc/test_suite_spec.tex for more information on the template file
54     format."""
55
56     def __init__(self, inname):
57         self.inname = inname
58         # Initalise the replacements hash.
59         # Map of name to values.
60         self.replacements = { }
61         # Initalise the function list hash.
62         self.functions = []
63         # Emit the suite wrapper into a temporary file
64         self.tmpname = tempfile.mktemp()
65         (self.dirname, self.filename) = os.path.split(self.inname)
66         (self.basename, self.ext) = os.path.splitext (self.filename)
67
68     def permute(self, basepath, keys, trans = {}):
69         """Permutes across all of the names.  For each value, recursivly creates
70         a mangled form of the name, this value, and all the combinations of
71         the remaining values.  At the tail of the recursion when one full
72         combination is built, generates an instance of the test case from
73         the template."""
74         if len(keys) == 0:
75             # End of the recursion.
76             # Set the runtime substitutions.
77             trans['testcase'] = re.sub(r'\\', r'\\\\', basepath)
78             # Create the instance from the template
79             T = TemplateDocument(self.tmpname)
80             T.substitutions = trans
81             T.write(basepath + self.ext)
82         else:
83             # Pull off this key, then recursivly iterate through the rest.
84             key = keys[0]
85             for part in self.replacements[key]:
86                 trans[key] = part
87                 # Turn a empty string into something decent for a filename
88                 if not part:
89                     part = 'none'
90                 # Remove any bad characters from the filename.
91                 part = re.sub(r'\s+', r'_', part)
92                 # The slice operator (keys[1:]) creates a copy of the list missing the
93                 # first element.
94                 # Can't use '-' as a seperator due to the mcs51 assembler.
95                 self.permute(basepath + '_' + key + '_' + part, keys[1:], trans) 
96
97     def writetemplate(self):
98         """Given a template file and a temporary name writes out a verbatim copy
99         of the source file and adds the suite table and functions."""
100         fout = open(self.tmpname, 'w')
101
102         for line in self.lines:
103             fout.write(line)
104
105         # Emmit the suite table
106         fout.write(testfuntableheader)
107
108         n = 0;
109         for fun in self.functions:
110             # Turn the function definition into a pointer
111             fun = re.sub(r'\(\w+\)', '', fun)
112             fout.write("  __prints(\"Running " + fun + "\\n\");\n");
113             fout.write('  ' + fun + "();\n")
114             n += 1;
115
116         fout.write(testfuntablefooter)
117         fout.write("\nconst int __numCases = " + str(n) + ";\n")
118         fout.write(testfunsuite);
119         
120         fout.close()
121         return n
122
123     def readfile(self):
124         """Read in all of the input file."""
125         fin = open(self.inname)
126         self.lines = fin.readlines()
127         fin.close()
128
129     def parse(self):
130         # Start off in the header.
131         inheader = 1;
132
133         # Iterate over the source file and pull out the meta data.
134         for line in self.lines:
135             line = trim(line)
136
137             # If we are still in the header, see if this is a substitution line
138             if inheader:
139                 # A substitution line has a ':' in it
140                 if re.search(r':', line) != None:
141                     # Split out the name from the values
142                     (name, rawvalues) = re.split(r':', line)
143                     # Split the values at the commas
144                     values = re.split(r',', rawvalues)
145                     
146                     # Trim the name
147                     name = trim(name)
148                     # Trim all the values
149                     values = map(trim, values)
150                     
151                     self.replacements[name] = values
152                 elif re.search(r'\*/', line) != None:
153                     # Hit the end of the comments
154                     inheader = 0;
155                 else:
156                     # Do nothing.
157                     None
158             else:
159                 # Pull out any test function names
160                 if re.search(r'^\W*test\w*\W*\(\W*void\W*\)', line) != None:
161                     self.functions.append(line)
162
163     def generate(self):
164         """Main function.  Generates all of the instances."""
165         self.readfile()
166         self.parse()
167         if self.writetemplate() == 0:
168             sys.stderr.write("Empty function list in " + outdir + "!\n")
169
170         # Create the output directory if it doesn't exist
171         createdir(outdir)
172
173         # Generate
174         self.permute(os.path.join(outdir, self.basename), self.replacements.keys())
175
176         # Remove the temporary file
177         os.remove(self.tmpname)
178
179 def main():
180     # Check and parse the command line arguments
181     if len(sys.argv) < 3:
182         print "usage: generate-cases.py template.c outdir"
183         sys.exit(-1)
184         
185     # Input name is the first arg.
186
187     s = InstanceGenerator(sys.argv[1])
188     s.generate()
189
190 if __name__ == '__main__':
191     main()