summaryrefslogtreecommitdiff
path: root/test-chill/testchill/chill.py
blob: 0e301496ad8eb6a7ef242bbd84e808b39ad3d18a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
#TODO: Re-Document
#TODO: highlight test implementation hooks

import os
import os.path

from . import gcov
from . import test
from . import util
from . import cpp_validate


class ChillConfig(object):
    def __init__(self, chill_dir=None, bin_dir=None, build_cuda=False, script_lang=None):
        self.build_cuda = build_cuda
        self.script_lang = script_lang
        self.chill_dir = chill_dir
        self.bin_dir = bin_dir
        if self.script_lang is None:
            self.script_lang = self.default_script_lang()
    
    def default_script_lang(self):
        return 'python'
    
    def _buildfunc(self, cc, link=True):
        if not link:
            compile_args = ['-c -Wuninitialized']
        elif link and cc == 'nvcc':
            compile_args = ['-L/usr/local/cuda/lib64/lib', '-lcuda', '-lcudart', '-lstdc++', '-lrt', '-Wuninitialized']
        else:
            compile_args = ['-lstdc++', '-lrt', '-Wuninitialized']
        
        def build(src, dest, args=[], defines={}, wd=None):
            if wd is None:
                wd = os.path.dirname(src)
            args += ['-D{}={}'.format(k,v) for k, v in defines.items()]
            dest = os.path.join(wd, dest)
            stdout = util.shell(cc, args + [src, '-o', dest] + compile_args, wd=wd)
            return dest, stdout
        return build
    
    def compile_src_func(self):
        return self._buildfunc('gcc', False)
    
    def compile_gensrc_func(self):
        if self.build_cuda:
            return self._buildfunc('nvcc', False)
        else:
            return self._buildfunc('gcc', False)
    
    def build_src_func(self):
        return self._buildfunc('gcc')
    
    def build_gensrc_func(self):
        if self.build_cuda:
            return self._buildfunc('nvcc')
        else:
            return self._buildfunc('gcc')
    
    @property
    def config_args(self):
        args = []
        if self.build_cuda:
            args += ['--enable-cuda']
        if self.script_lang is not None:
            args += ['--with-' + self.script_lang]
        return args
    
    @property
    def buildname(self):
        if self.build_cuda:
            return 'cudachill'
        else:
            return 'chill'
    
    @property
    def name(self):
        if self.buildname == 'cudachill':
            return 'cuda-chill-' + self.script_lang
        else:
            return 'chill-' + self.script_lang
    
    @staticmethod
    def ext_to_script_lang(ext):
        return {'script':'script', 'lua':'lua', 'py':'python'}[ext]
    
    @staticmethod
    def configs(chill_dir, bin_dir, build_cuda=None, script_lang=None):
        all_configs = [
                (False, 'script'),
                (False, 'lua'),
                (False, 'python'),
                (True, 'lua'),
                (True, 'python')]
                
        pred_list = [lambda x: True]
        if not build_cuda is None:
            pred_list += [lambda x: x[0] == build_cuda]
        if not script_lang is None:
            pred_list += [lambda x: x[1] == script_lang]
        
        cond = lambda x: all(p(x) for p in pred_list)
        
        return iter(ChillConfig(chill_dir, bin_dir, *conf) for conf in filter(cond, all_configs))


# -                               - #
# -  Test case for building chill - #
# -                               - #
class BuildChillTestCase(test.TestCase):
    """
    Test case for building chill.
    """
    
    default_options = {
            'coverage': False   # compile for coverage
        }
    
    def __init__(self, config, options={}, coverage_set=None):
        """
        @param config chill configuration object
        @param options options for building chill and testing the build process
        @param coverage_set GcovSet object to record coverage
        """
        assert isinstance(config, ChillConfig)
        if config.script_lang == None:
            config.script_lang = config.default_script_lang()
        self.config = config
        super(BuildChillTestCase,self).__init__(self.config.name)
        self._set_options(options, coverage_set)
    
    def _set_options(self, options, coverage_set):
        self.options = dict(BuildChillTestCase.default_options)
        self.options.update(options)
        
        if self.options['coverage']:
            coverage_set.addprogram(self.config.name, self.config.chill_dir)
    
    def setUp(self):
        """
        Called before run, outside of the context of a test case
        """
        # clean up any coverage files from a previous build
        util.shell('rm', ['-f', '*.gcno'], wd=self.config.chill_dir)
        util.shell('rm', ['-f', '*.gcov'], wd=self.config.chill_dir)
        util.shell('rm', ['-f', '*.gcda'], wd=self.config.chill_dir)
        
        util.shell('make clean', wd=self.config.chill_dir)
        util.shell('make veryclean', wd=self.config.chill_dir)
    
    def run(self):
        """
        Build chill
        """
        util.shell('make', ['clean'], wd=self.config.chill_dir)
        util.shell('./configure', self.config.config_args, wd=self.config.chill_dir)
        util.shell('make', [], wd=self.config.chill_dir)
        
        #util.shell('make', ['clean'], wd=self.config.chill_dir)
        #util.shell('make', ['veryclean'], wd=self.config.chill_dir)
        #util.shell('make', [depend_target] + [self.build_args], env=self.build_env, wd=self.config.chill_dir)
        #util.shell('make', [target] + [self.build_args], env=self.build_env, wd=self.config.chill_dir)
        return self.make_pass()
        
    def tearDown(self):
        """
        Called after run, outside of the context of a test case.
        If a binary directory is specified, rename and move the executable there, otherwise, just rename it.
        """
        if self.test_result.passed():
            if self.config.bin_dir:
                util.shell('mv', [os.path.join(self.config.chill_dir, self.config.buildname), os.path.join(self.config.bin_dir, self.config.name)])
            elif not self.config.buildname == name:
                util.shell('mv', [os.path.join(self.config.chill_dir, self.config.buildname), os.path.join(self.config.chill_dir, self.config.name)])


# -                              - #
# -  Test case for running chill - #
# -                              - #
class RunChillTestCase(test.SequencialTestCase):
    """
    Test case for running and testing chill.
    """
    
    default_options={
            'compile-src':True,              # Compile original source file
            'run-script':True,               # Run chill script
            'compile-gensrc':True,           # Compile generated source file
            'check-run-script-stdout':False, # Diff stdout from run_script() against an expected value (from a .stdout file)
            'coverage':False,                # Record coverage
            
            'fail-compile-src':False,        # Expect compile_src to fail (TODO: not implemented)
            'fail-run-script':False,         # Expect run_script to fail  (TODO: not implemented)
        }
    
    def __init__(self, config, chill_script, chill_src, wd=None, options={}, coverage_set=None):
        """
        @param config Chill configuration object
        @param chill_script The path to the chill script.
        @param chill_src The path to the source file that the script uses.
        @param wd The working directory. Where the script will be executed, compiled, and tested.
        @param options Additional testing options.
        @param coverage_set GcovSet object to record coverage
        """
        if config.script_lang == None:
            config.script_lang = ChillConfig.ext_to_script_lang(chill_script.split('.')[-1])
        
        assert isinstance(config, ChillConfig)
        
        super(RunChillTestCase,self).__init__(config.name + ':' + os.path.basename(chill_script))
        
        self.config = config
        self.wd = wd if (wd != None) else os.getcwd()
        
        self.chill_src_path = os.path.abspath(chill_src)
        self.chill_script_path = os.path.abspath(chill_script)
        self.chill_bin = os.path.join(self.config.bin_dir, self.config.name)
        self.chill_src = os.path.basename(self.chill_src_path)
        self.chill_script = os.path.basename(self.chill_script_path)
        self.chill_gensrc = self._get_gensrc(self.chill_src)
        self.chill_gensrc_path = os.path.join(self.wd, self.chill_gensrc)
        
        self.compile_src_func = self.config.compile_src_func()
        self.compile_gensrc_func = self.config.compile_gensrc_func()
        self.build_src_func = self.config.build_src_func()
        self.build_gensrc_func = self.config.build_gensrc_func()
        
        self._set_options(options, coverage_set)

    def _set_options(self, options, coverage_set=None):
        self.options = dict(RunChillTestCase.default_options)
        self.options.update(options)
        
        self.out = dict()
        self.expected = dict()
        
        if self.options['compile-src']:
            self.add_subtest('compile-src', self.compile_src)
        if self.options['run-script']:
            self.add_subtest('run-script', self.run_script)
        if self.options['compile-gensrc']:
            self.add_subtest('compile-generated-src', self.compile_gensrc)
        self.add_subtest('check-run-script-validate', self.check_run_script_validate)
        if self.options['check-run-script-stdout']:
            self.add_subtest('check-run-script-stdout', self.check_run_script_stdout)
            with open('.'.join(self.chill_script_path.split('.')[0:-1] + ['stdout']), 'r') as f:
                self.expected['run_script.stdout'] = f.read()
        self.coverage_set = coverage_set
    
    def _get_gensrc(self, src):
        """
        The name of the generated source file.
        """
        if not self.config.build_cuda:
            return 'rose_' + src
        else:
            return 'rose_' + '.'.join(src.split('.')[0:-1]) + '.cu'
    
    def setUp(self):
        """
        Called before any tests are performed. Moves source and script files into the working directory
        and removes any gcov data files
        """
        util.shell('cp', [self.chill_src_path, self.chill_src], wd=self.wd)
        util.shell('cp', [self.chill_script_path, self.chill_script], wd=self.wd)
        #TODO: check for chill binary
    
    def tearDown(self):
        """
        Called when the test is complete
        """
        util.shell('rm', ['-f', self.chill_src], wd=self.wd)
        util.shell('rm', ['-f', self.chill_script], wd=self.wd)
        util.shell('rm', ['-f', self.chill_gensrc], wd=self.wd)
        if self.options['coverage'] and self.coverage_set is not None:
            self.coverage_set.addcoverage(self.config.name, self.name)
    
    # -             - #
    # - Chill Tests - #
    # -             - #
    
    def compile_src(self, tc):
        """
        Attempts to compile the source file before any transformation is performed. Fails if gcc fails.
        """
        #self.out['compile_src.stdout'] = util.shell('gcc', ['-c', self.chill_src], wd=self.wd)
        _, self.out['compile_src.stdout'] = self.compile_src_func(self.chill_src, util.mktemp(), wd=self.wd)
        return tc.make_pass()
    
    def run_script(self, tc):
        """
        Attempts to run the script file. Fails if chill exits with a non-zero result.
        """
        # look for cudaize.lua for cuda-chill
        if self.config.build_cuda and not os.path.exists(os.path.join(self.wd, 'cudaize.lua')):
            return test.TestResult.make_error(test.FailedTestResult, tc, reason='cudaize.lua was missing from the working directory.')
        self.out['run_script.stdout'] = util.shell(self.chill_bin, [self.chill_script], wd=self.wd)
        return tc.make_pass()
    
    def compile_gensrc(self, tc):
        """
        Attempts to compile the generated source file. Fails if gcc fails.
        """
        #self.out['compile_gensrc.stdout'] = util.shell('gcc', ['-c', self.chill_gensrc], wd=self.wd)
        _, self.out['compile_gensrc.stdout'] = self.compile_gensrc_func(self.chill_gensrc_path, util.mktemp(), wd=self.wd)
        return tc.make_pass()
    
    def check_run_script_validate(self, tc):
        """
        Generate test data and run both the original source and generated source against it.
        Fail if any test procedure generates different output.
        """
        for name, (is_valid, is_faster) in cpp_validate.run_from_src(self.chill_src, self.chill_gensrc, self.build_src_func, self.build_gensrc_func, wd=self.wd):
            self.out['check_run_script_validate.{}'.format(name)] = (is_valid, is_faster)
            if not is_valid:
                return tc.make_fail('test procedure {} returned invalid results.'.format(name))
        return tc.make_pass()
    
    def check_run_script_stdout(self, tc):
        """
        Diff stdout from run_script against an expected stdout
        """
        isdiff, diff = util.isdiff(self.out['run_script.stdout'], self.expected['run_script.stdout'])
        if isdiff:
            return test.TestResult.make_fail(test.FailedTestResult, tc, reason='Diff:\n' + diff)
        return tc.make_pass()