~azzar1/unity/add-show-desktop-key

294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
1
# IVLE - Informatics Virtual Learning Environment
2
# Copyright (C) 2007-2008 The University of Melbourne
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
18
# Module: TestFramework
19
# Author: Dilshan Angampitiya
507 by stevenbird
Extended code test framework to support tests on the code string, rather
20
#         Steven Bird (revisions)
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
21
# Date:   24/1/2008
22
23
# Brief description of the Module# define custom exceptions
24
# use exceptions for all errors found in testing
25
1034 by dcoles
Console: Refactored the code to support supplying of stdin to the console.
26
import sys, copy
507 by stevenbird
Extended code test framework to support tests on the code string, rather
27
import types
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
28
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
29
from ivle import testfilespace
1029 by dcoles
Tutorial Service: Ported the tutorial service to the console so that all
30
306 by dilshan_a
Consolidated SolutionError and AttemptError into ScriptExecutionError.
31
# student error or author error
32
# errors in student code get handled internally
33
# errors in solution code get passed up
34
class ScriptExecutionError(Exception):
35
    """Runtime error in the student code or solution code"""
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
36
    def __init__(self, exc_info):
37
        cla, exc, trbk = exc_info
38
        self._name = cla.__name__
39
        self._detail = str(exc)
310 by dilshan_a
Add lineno to exception info.
40
        self._trbk = trbk
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
41
42
    def is_critical(self):
43
        if (    self._name == 'FunctionNotFoundError'
44
            or  self._name == 'SyntaxError'
45
            or  self._name == 'IndentationError'):
46
            return True
47
        else:
48
            return False
49
50
    def to_dict(self):
310 by dilshan_a
Add lineno to exception info.
51
        import traceback
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
52
        return {'name': self._name,
53
                'detail': self._detail,
311 by dilshan_a
Last commit was buggy.
54
                'critical': self.is_critical(),
310 by dilshan_a
Add lineno to exception info.
55
                'lineno': traceback.tb_lineno(self._trbk)
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
56
                }
57
58
    def __str__(self):
59
        return self._name + " - " + str(self._detail)
60
306 by dilshan_a
Consolidated SolutionError and AttemptError into ScriptExecutionError.
61
# author error
62
class TestCreationError(Exception):
63
    """An error occured while creating the test suite or one of its components"""
64
    def __init__(self, reason):
65
        self._reason = reason
66
        
67
    def __str__(self):
68
        return self._reason
69
70
# author error
71
class TestError(Exception):
72
    """Runtime error in the testing framework outside of the provided or student code"""
73
    def __init__(self, exc_info):
74
        cla, exc, trbk = exc_info
75
        self._name = cla.__name__
76
        self._detail = str(exc)
77
        self._exc_info = exc_info
78
79
    def exc_info(self):
80
        return self._exc_info
81
82
    def __str__(self):
83
        return "Error testing solution against attempt: %s - %s" %(self._name, self._detail)
84
85
# author error
86
# raised when expected file not found in solution output
87
# Always gets caught and passed up as a TestError
88
class FileNotFoundError(Exception):
89
    def __init__(self, filename):
90
        self._filename = filename
91
92
    def __str__(self):
93
        return "File %s not found in output" %(self._filename)
94
    
95
96
# Error encountered when executing solution or attempt code
97
# Always gets caught and passed up in a ScriptExecutionError
299 by dilshan_a
Test framework now handles exceptions as valid outputs for scripts.
98
class FunctionNotFoundError(Exception):
99
    """This error is returned when a function was expected in a
100
    test case but was not found"""
101
    def __init__(self, function_name):
102
        self.function_name = function_name
103
104
    def __str__(self):
105
        return "Function " + self.function_name + " not found"
106
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
107
class TestCasePart:
108
    """
109
    A part of a test case which compares a subset of the input files or file streams.
502 by stevenbird
www/apps/tutorialservice/__init__.py
110
    This can be done either with a comparison function, or by comparing directly, after
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
111
    applying normalisations.
112
    """
495 by stevenbird
Fixes to permit content authors to produce nicer diagnostic responses as a result
113
114
    ident = classmethod(lambda x: x)
115
    ignore = classmethod(lambda x: None)
116
    match = classmethod(lambda x,y: x==y)
117
    always_match = classmethod(lambda x,y: True)
118
    true = classmethod(lambda *x: True)
119
    false = classmethod(lambda *x: False)
120
502 by stevenbird
www/apps/tutorialservice/__init__.py
121
    def __init__(self, pass_msg, fail_msg, default='match'):
122
        """Initialise with descriptions (pass,fail) and a default behavior for output
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
123
        If default is match, unspecified files are matched exactly
124
        If default is ignore, unspecified files are ignored
125
        The default default is match.
126
        """
502 by stevenbird
www/apps/tutorialservice/__init__.py
127
        self._pass_msg = pass_msg
128
        self._fail_msg = fail_msg
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
129
        self._default = default
130
        if default == 'ignore':
495 by stevenbird
Fixes to permit content authors to produce nicer diagnostic responses as a result
131
            self._default_func = self.true
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
132
        else:
495 by stevenbird
Fixes to permit content authors to produce nicer diagnostic responses as a result
133
            self._default_func = self.match
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
134
135
        self._file_tests = {}
136
        self._stdout_test = ('check', self._default_func)
137
        self._stderr_test = ('check', self._default_func)
299 by dilshan_a
Test framework now handles exceptions as valid outputs for scripts.
138
        self._exception_test = ('check', self._default_func)
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
139
        self._result_test = ('check', self._default_func)
502 by stevenbird
www/apps/tutorialservice/__init__.py
140
        self._code_test = ('check', self._default_func)
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
141
142
    def _set_default_function(self, function, test_type):
143
        """"Ensure test type is valid and set function to a default
144
        if not specified"""
145
        
146
        if test_type not in ['norm', 'check']:
147
            raise TestCreationError("Invalid test type in %s" %self._desc)
148
        
149
        if function == '':
495 by stevenbird
Fixes to permit content authors to produce nicer diagnostic responses as a result
150
            if test_type == 'norm': function = self.ident
151
            else: function = self.match
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
152
153
        return function
154
155
    def _validate_function(self, function, included_code):
156
        """Create a function object from the given string.
502 by stevenbird
www/apps/tutorialservice/__init__.py
157
        If a valid function object cannot be created, raise an error.
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
158
        """
159
        if not callable(function):
160
            try:
161
                exec "__f__ = %s" %function in included_code
162
            except:
507 by stevenbird
Extended code test framework to support tests on the code string, rather
163
                raise TestCreationError("Invalid function %s" % function)
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
164
165
            f = included_code['__f__']
166
167
            if not callable(f):
507 by stevenbird
Extended code test framework to support tests on the code string, rather
168
                raise TestCreationError("Invalid function %s" % function)
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
169
        else:
170
            f = function
171
172
        return f
173
174
    def validate_functions(self, included_code):
175
        """Ensure all functions used by the test cases exist and are callable.
507 by stevenbird
Extended code test framework to support tests on the code string, rather
176
        Also convert their string representations to function objects.
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
177
        This can only be done once all the include code has been specified.
178
        """
179
        (test_type, function) = self._stdout_test
180
        self._stdout_test = (test_type, self._validate_function(function, included_code))
181
        
182
        (test_type, function) = self._stderr_test
183
        self._stderr_test = (test_type, self._validate_function(function, included_code))
716 by mattgiuca
Test Framework: Numerous bug fixes.
184
        
185
        (test_type, function) = self._result_test
186
        self._result_test = (test_type, self._validate_function(function, included_code))
187
        
188
        (test_type, function) = self._exception_test
189
        self._exception_test = (test_type, self._validate_function(function, included_code))
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
190
191
        for filename, (test_type, function) in self._file_tests.items():
192
            self._file_tests[filename] = (test_type, self._validate_function(function, included_code))
193
            
194
    def add_result_test(self, function, test_type='norm'):
195
        "Test part that compares function return values"
196
        function = self._set_default_function(function, test_type)
197
        self._result_test = (test_type, function)
198
            
199
    def add_stdout_test(self, function, test_type='norm'):
200
        "Test part that compares stdout"
201
        function = self._set_default_function(function, test_type)
202
        self._stdout_test = (test_type, function)
203
204
    def add_stderr_test(self, function, test_type='norm'):
205
        "Test part that compares stderr"
206
        function = self._set_default_function(function, test_type)
207
        self._stderr_test = (test_type, function)
208
299 by dilshan_a
Test framework now handles exceptions as valid outputs for scripts.
209
    def add_exception_test(self, function, test_type='norm'):
210
        "Test part that compares stderr"
211
        function = self._set_default_function(function, test_type)
212
        self._exception_test = (test_type, function)
213
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
214
    def add_file_test(self, filename, function, test_type='norm'):
215
        "Test part that compares the contents of a specified file"
216
        function = self._set_default_function(function, test_type)
217
        self._file_tests[filename] = (test_type, function)
218
502 by stevenbird
www/apps/tutorialservice/__init__.py
219
    def add_code_test(self, function, test_type='norm'):
220
        "Test part that examines the supplied code"
221
        function = self._set_default_function(function, test_type)
222
        self._code_test = (test_type, function)
223
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
224
    def _check_output(self, solution_output, attempt_output, test_type, f):
225
        """Compare solution output and attempt output using the
502 by stevenbird
www/apps/tutorialservice/__init__.py
226
        specified comparison function.
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
227
        """
507 by stevenbird
Extended code test framework to support tests on the code string, rather
228
        solution_output = str(solution_output)
229
        attempt_output = str(attempt_output)
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
230
            
231
        if test_type == 'norm':
232
            return f(solution_output) == f(attempt_output)
233
        else:
234
            return f(solution_output, attempt_output)
235
519 by stevenbird
Fixed bug in test framework. Code given in the <include> section was
236
    def _check_code(self, solution, attempt, test_type, f, include_space):
502 by stevenbird
www/apps/tutorialservice/__init__.py
237
        """Compare solution code and attempt code using the
238
        specified comparison function.
239
        """
507 by stevenbird
Extended code test framework to support tests on the code string, rather
240
        if type(f) in types.StringTypes:  # kludge
519 by stevenbird
Fixed bug in test framework. Code given in the <include> section was
241
            f = eval(str(f), include_space)
502 by stevenbird
www/apps/tutorialservice/__init__.py
242
        if test_type == 'norm':
243
            return f(solution) == f(attempt)
244
        else:
245
            return f(solution, attempt)
246
519 by stevenbird
Fixed bug in test framework. Code given in the <include> section was
247
    def run(self, solution_data, attempt_data, include_space):
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
248
        """Run the tests to compare the solution and attempt data
328 by mattgiuca
console: Renamed HTML element IDs to prefix "console_".
249
        Returns the empty string if the test passes, or else an error message.
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
250
        """
251
502 by stevenbird
www/apps/tutorialservice/__init__.py
252
        # check source code itself
253
        (test_type, f) = self._code_test
519 by stevenbird
Fixed bug in test framework. Code given in the <include> section was
254
        if not self._check_code(solution_data['code'], attempt_data['code'], test_type, f, include_space):       
502 by stevenbird
www/apps/tutorialservice/__init__.py
255
            return 'Unexpected code'
256
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
257
        # check function return value (None for scripts)
258
        (test_type, f) = self._result_test
507 by stevenbird
Extended code test framework to support tests on the code string, rather
259
        if not self._check_output(solution_data['result'], attempt_data['result'], test_type, f):
502 by stevenbird
www/apps/tutorialservice/__init__.py
260
            return 'Unexpected function return value'
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
261
262
        # check stdout
263
        (test_type, f) = self._stdout_test
507 by stevenbird
Extended code test framework to support tests on the code string, rather
264
        if not self._check_output(solution_data['stdout'], attempt_data['stdout'], test_type, f):
502 by stevenbird
www/apps/tutorialservice/__init__.py
265
            return 'Unexpected output'
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
266
267
        #check stderr
268
        (test_type, f) = self._stderr_test
507 by stevenbird
Extended code test framework to support tests on the code string, rather
269
        if not self._check_output(solution_data['stderr'], attempt_data['stderr'], test_type, f):
502 by stevenbird
www/apps/tutorialservice/__init__.py
270
            return 'Unexpected error output'
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
271
299 by dilshan_a
Test framework now handles exceptions as valid outputs for scripts.
272
        #check exception
273
        (test_type, f) = self._exception_test
507 by stevenbird
Extended code test framework to support tests on the code string, rather
274
        if not self._check_output(solution_data['exception'], attempt_data['exception'], test_type, f):
502 by stevenbird
www/apps/tutorialservice/__init__.py
275
            return 'Unexpected exception'
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
276
277
        solution_files = solution_data['modified_files']
278
        attempt_files = attempt_data['modified_files']
279
280
        # check files indicated by test
281
        for (filename, (test_type, f)) in self._file_tests.items():
282
            if filename not in solution_files:
306 by dilshan_a
Consolidated SolutionError and AttemptError into ScriptExecutionError.
283
                raise FileNotFoundError(filename)
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
284
            elif filename not in attempt_files:
285
                return filename + ' not found'
286
            elif not self._check_output(solution_files[filename], attempt_files[filename], test_type, f):
287
                return filename + ' does not match'
288
289
        if self._default == 'ignore':
290
            return ''
291
292
        # check files found in solution, but not indicated by test
293
        for filename in [f for f in solution_files if f not in self._file_tests]:
294
            if filename not in attempt_files:
295
                return filename + ' not found'
495 by stevenbird
Fixes to permit content authors to produce nicer diagnostic responses as a result
296
            elif not self._check_output(solution_files[filename], attempt_files[filename], 'match', self.match):
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
297
                return filename + ' does not match'
298
299
        # check if attempt has any extra files
300
        for filename in [f for f in attempt_files if f not in solution_files]:
301
            return "Unexpected file found: " + filename
302
303
        # Everything passed with no problems
304
        return ''
305
        
306
class TestCase:
307
    """
308
    A set of tests with a common inputs
309
    """
1029 by dcoles
Tutorial Service: Ported the tutorial service to the console so that all
310
    def __init__(self, console, name='', function=None, stdin='', filespace=None, global_space=None):
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
311
        """Initialise with name and optionally, a function to test (instead of the entire script)
312
        The inputs stdin, the filespace and global variables can also be specified at
313
        initialisation, but may also be set later.
314
        """
1029 by dcoles
Tutorial Service: Ported the tutorial service to the console so that all
315
        self._console = console
316
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
317
        if global_space == None:
318
            global_space = {}
319
        if filespace == None:
320
            filespace = {}
321
        
322
        self._name = name
323
        
324
        if function == '': function = None
325
        self._function = function
326
        self._list_args = []
327
        self._keyword_args = {}
328
        
1035 by dcoles
Tutorial: Added in stdin support for exercises (sets them up in console)
329
        self.set_stdin(stdin)
1034 by dcoles
Console: Refactored the code to support supplying of stdin to the console.
330
        self._filespace = testfilespace.TestFilespace(filespace)
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
331
        self._global_space = global_space
332
        self._parts = []
299 by dilshan_a
Test framework now handles exceptions as valid outputs for scripts.
333
        self._allowed_exceptions = set()
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
334
335
    def set_stdin(self, stdin):
336
        """ Set the given string as the stdin for this test case"""
1035 by dcoles
Tutorial: Added in stdin support for exercises (sets them up in console)
337
        # stdin must have a newline at the end for raw_input to work properly
338
        if stdin[-1:] != '\n':
339
            stdin += '\n'
1036 by dcoles
TutorialService: Fix up bug in setting of stdin (would crash scripts)
340
        self._stdin = stdin
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
341
342
    def add_file(self, filename, data):
343
        """ Insert the given filename-data pair into the filespace for this test case"""
1029 by dcoles
Tutorial Service: Ported the tutorial service to the console so that all
344
        # TODO: Add the file to the console
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
345
        self._filespace.add_file(filename, data)
346
        
347
    def add_variable(self, variable, value):
348
        """ Add the given varibale-value pair to the initial global environment
1029 by dcoles
Tutorial Service: Ported the tutorial service to the console so that all
349
        for this test case. The value is the string repr() of an actual value.
350
        Throw an exception if the value cannot be paresed.
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
351
        """
352
        
353
        try:
354
            self._global_space[variable] = eval(value)
355
        except:
356
            raise TestCreationError("Invalid value for variable %s: %s" %(variable, value))
357
358
    def add_arg(self, value, name=None):
359
        """ Add a value to the argument list. This only applies when testing functions.
360
        By default arguments are not named, but if they are, they become keyword arguments.
361
        """
362
        try:
363
            if name == None or name == '':
364
                self._list_args.append(eval(value))
365
            else:
366
                self._keyword_args[name] = value
367
        except:
368
            raise TestCreationError("Invalid value for function argument: %s" %value)
299 by dilshan_a
Test framework now handles exceptions as valid outputs for scripts.
369
370
    def add_exception(self, exception_name):
371
        self._allowed_exceptions.add(exception_name)
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
372
        
373
    def add_part(self, test_part):
374
        """ Add a TestPart to this test case"""
375
        self._parts.append(test_part)
376
377
    def validate_functions(self, included_code):
378
        """ Validate all the functions in each part in this test case
379
        This can only be done once all the include code has been specified.
380
        """
381
        for part in self._parts:
382
            part.validate_functions(included_code)
383
384
    def get_name(self):
385
        """ Get the name of the test case """
386
        return self._name
387
519 by stevenbird
Fixed bug in test framework. Code given in the <include> section was
388
    def run(self, solution, attempt_code, include_space, stop_on_fail=True):
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
389
        """ Run the solution and the attempt with the inputs specified for this test case.
390
        Then pass the outputs to each test part and collate the results.
391
        """
392
        case_dict = {}
393
        case_dict['name'] = self._name
394
        
395
        # Run solution
396
        try:
397
            global_space_copy = copy.deepcopy(self._global_space)
398
            solution_data = self._execstring(solution, global_space_copy)
1035 by dcoles
Tutorial: Added in stdin support for exercises (sets them up in console)
399
            self._console.stdin.truncate(0)
400
            self._console.stdin.write(self._stdin)
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
401
            
402
            # if we are just testing a function
403
            if not self._function == None:
1029 by dcoles
Tutorial Service: Ported the tutorial service to the console so that all
404
                if self._function not in solution_data['globals']:
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
405
                    raise FunctionNotFoundError(self._function)
1029 by dcoles
Tutorial Service: Ported the tutorial service to the console so that all
406
                solution_data = self._run_function(self._function,
407
                    self._list_args, self._keyword_args, solution)
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
408
                
409
        except:
306 by dilshan_a
Consolidated SolutionError and AttemptError into ScriptExecutionError.
410
            raise ScriptExecutionError(sys.exc_info())
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
411
412
        # Run student attempt
413
        try:
414
            global_space_copy = copy.deepcopy(self._global_space)
302 by dilshan_a
Updated test framework so that student's code is passed in as a string.
415
            attempt_data = self._execstring(attempt_code, global_space_copy)
1035 by dcoles
Tutorial: Added in stdin support for exercises (sets them up in console)
416
            self._console.stdin.truncate(0)
417
            self._console.stdin.write(self._stdin)
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
418
            
419
            # if we are just testing a function
420
            if not self._function == None:
1029 by dcoles
Tutorial Service: Ported the tutorial service to the console so that all
421
                if self._function not in attempt_data['globals']:
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
422
                    raise FunctionNotFoundError(self._function)
1029 by dcoles
Tutorial Service: Ported the tutorial service to the console so that all
423
                attempt_data = self._run_function(self._function,
424
                    self._list_args, self._keyword_args, attempt_code)
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
425
        except:
306 by dilshan_a
Consolidated SolutionError and AttemptError into ScriptExecutionError.
426
            case_dict['exception'] = ScriptExecutionError(sys.exc_info()).to_dict()
304 by dilshan_a
Added documentation of output of TestSuite.
427
            case_dict['passed'] = False
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
428
            return case_dict
429
        
430
        results = []
304 by dilshan_a
Added documentation of output of TestSuite.
431
        passed = True
432
        
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
433
        # generate results
434
        for test_part in self._parts:
306 by dilshan_a
Consolidated SolutionError and AttemptError into ScriptExecutionError.
435
            try:
519 by stevenbird
Fixed bug in test framework. Code given in the <include> section was
436
                result = test_part.run(solution_data, attempt_data, include_space)
306 by dilshan_a
Consolidated SolutionError and AttemptError into ScriptExecutionError.
437
            except:
438
                raise TestError(sys.exc_info())
439
            
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
440
            result_dict = {}
502 by stevenbird
www/apps/tutorialservice/__init__.py
441
            result_dict['description'] = test_part._pass_msg
495 by stevenbird
Fixes to permit content authors to produce nicer diagnostic responses as a result
442
            result_dict['passed'] = (result == '')
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
443
            if result_dict['passed'] == False:
444
                result_dict['error_message'] = result
502 by stevenbird
www/apps/tutorialservice/__init__.py
445
                result_dict['description'] = test_part._fail_msg
304 by dilshan_a
Added documentation of output of TestSuite.
446
                passed = False
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
447
                
448
            results.append(result_dict)
449
514 by stevenbird
More flexible control of test_case_parts via optional flag
450
            # Do we continue the test_parts after one of them has failed?
451
            if not passed and stop_on_fail:
452
                break;
453
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
454
        case_dict['parts'] = results
304 by dilshan_a
Added documentation of output of TestSuite.
455
        case_dict['passed'] = passed
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
456
457
        return case_dict
458
                
459
    def _execfile(self, filename, global_space):
460
        """ Execute the file given by 'filename' in global_space, and return the outputs. """
461
        self._initialise_global_space(global_space)
716 by mattgiuca
Test Framework: Numerous bug fixes.
462
        data = self._run_function(lambda: execfile(filename, global_space),
463
            code = open(filename).read())
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
464
        return data
465
466
    def _execstring(self, string, global_space):
1029 by dcoles
Tutorial Service: Ported the tutorial service to the console so that all
467
        """ Execute the given string in global_space, and return the outputs. 
468
        """
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
469
        self._initialise_global_space(global_space)
305 by dilshan_a
Neated up execution of strings in TestCase.
470
        
1034 by dcoles
Console: Refactored the code to support supplying of stdin to the console.
471
        inspection = self._console.execute(string)
1029 by dcoles
Tutorial Service: Ported the tutorial service to the console so that all
472
473
        exception_name = None
474
        if 'exception' in inspection:
475
            exception = inspection['exception']['except']
476
            exception_name = type(exception).__name__
477
            raise(exception)
478
479
        return {'code': string,
480
                'result': None,
1034 by dcoles
Console: Refactored the code to support supplying of stdin to the console.
481
                'globals': self._console.globals(),
1029 by dcoles
Tutorial Service: Ported the tutorial service to the console so that all
482
                'exception': exception_name, # Hmmm... odd? Is this right?
1034 by dcoles
Console: Refactored the code to support supplying of stdin to the console.
483
                'stdout': self._console.stdout.read(),
484
                'stderr': self._console.stderr.read(),
1029 by dcoles
Tutorial Service: Ported the tutorial service to the console so that all
485
                'modified_files': None}
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
486
487
    def _initialise_global_space(self, global_space):
488
        """ Modify the provided global_space so that file, open and raw_input are redefined
489
        to use our methods instead.
490
        """
1034 by dcoles
Console: Refactored the code to support supplying of stdin to the console.
491
        self._console.globals(global_space)
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
492
        self._current_filespace_copy = self._filespace.copy()
493
        global_space['file'] = lambda filename, mode='r', bufsize=-1: self._current_filespace_copy.openfile(filename, mode)
494
        global_space['open'] = global_space['file']
495
        global_space['raw_input'] = lambda x=None: raw_input()
496
        return global_space
497
1029 by dcoles
Tutorial Service: Ported the tutorial service to the console so that all
498
    def _run_function(self, function, args, kwargs, code):
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
499
        """ Run the provided function with the provided stdin, capturing stdout and stderr
500
        and the return value.
501
        Return all the output data.
716 by mattgiuca
Test Framework: Numerous bug fixes.
502
        code: The full text of the code, which needs to be stored as part of
503
        the returned dictionary.
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
504
        """
1029 by dcoles
Tutorial Service: Ported the tutorial service to the console so that all
505
        s_args = map(repr, args)
506
        s_kwargs = dict(zip(kwargs.keys(), map(repr, kwargs.values())))
507
        call = self._console.call(function, *s_args, **s_kwargs)
508
299 by dilshan_a
Test framework now handles exceptions as valid outputs for scripts.
509
        exception_name = None
1029 by dcoles
Tutorial Service: Ported the tutorial service to the console so that all
510
        if 'exception' in call:
511
            exception = call['exception']['except']
512
            exception_name = type(exception).__name__
513
            raise(exception)
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
514
716 by mattgiuca
Test Framework: Numerous bug fixes.
515
        return {'code': code,
1029 by dcoles
Tutorial Service: Ported the tutorial service to the console so that all
516
                'result': call['result'],
299 by dilshan_a
Test framework now handles exceptions as valid outputs for scripts.
517
                'exception': exception_name,
1036 by dcoles
TutorialService: Fix up bug in setting of stdin (would crash scripts)
518
                'stdout': self._console.stdout.read(),
519
                'stderr': self._console.stderr.read(),
1029 by dcoles
Tutorial Service: Ported the tutorial service to the console so that all
520
                'modified_files': None}
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
521
522
class TestSuite:
523
    """
513 by stevenbird
test/test_framework/*, exercises/sample/*
524
    The complete collection of test cases for a given exercise
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
525
    """
1029 by dcoles
Tutorial Service: Ported the tutorial service to the console so that all
526
    def __init__(self, name, console, solution=None):
513 by stevenbird
test/test_framework/*, exercises/sample/*
527
        """Initialise with the name of the test suite (the exercise name) and the solution.
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
528
        The solution may be specified later.
529
        """
530
        self._solution = solution
531
        self._name = name
532
        self._tests = []
1029 by dcoles
Tutorial Service: Ported the tutorial service to the console so that all
533
        self._console = console
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
534
        self.add_include_code("")
535
536
    def add_solution(self, solution):
513 by stevenbird
test/test_framework/*, exercises/sample/*
537
        " Specify the solution script for this exercise "
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
538
        self._solution = solution
539
540
    def has_solution(self):
523 by stevenbird
Adding ReStructured Text preprocessing of exercise descriptions,
541
        " Returns true if a solution has been provided "
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
542
        return self._solution != None
543
544
    def add_include_code(self, include_code = ''):
545
        """ Add include code that may be used by the test cases during
546
        comparison of outputs.
547
        """
548
        
549
        # if empty, make sure it can still be executed
550
        if include_code == "":
551
            include_code = "pass"
552
        self._include_code = str(include_code)
553
        
554
        include_space = {}
555
        try:
556
            exec self._include_code in include_space
557
        except:
558
            raise TestCreationError("Bad include code")
559
560
        self._include_space = include_space
561
    
562
    def add_case(self, test_case):
563
        """ Add a TestCase, then validate all functions inside test case
564
        now that the include code is known
565
        """
566
        self._tests.append(test_case)
567
        test_case.validate_functions(self._include_space)
568
502 by stevenbird
www/apps/tutorialservice/__init__.py
569
    def run_tests(self, attempt_code, stop_on_fail=False):
1029 by dcoles
Tutorial Service: Ported the tutorial service to the console so that all
570
        " Run all test cases on the specified console and collate the results "
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
571
        
513 by stevenbird
test/test_framework/*, exercises/sample/*
572
        exercise_dict = {}
573
        exercise_dict['name'] = self._name
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
574
        
575
        test_case_results = []
309 by dilshan_a
Added a passed key to return value of problem suite.
576
        passed = True
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
577
        for test in self._tests:
519 by stevenbird
Fixed bug in test framework. Code given in the <include> section was
578
            result_dict = test.run(self._solution, attempt_code, self._include_space)
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
579
            if 'exception' in result_dict and result_dict['exception']['critical']:
580
                # critical error occured, running more cases is useless
581
                # FunctionNotFound, Syntax, Indentation
513 by stevenbird
test/test_framework/*, exercises/sample/*
582
                exercise_dict['critical_error'] = result_dict['exception']
583
                exercise_dict['passed'] = False
584
                return exercise_dict
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
585
            
586
            test_case_results.append(result_dict)
304 by dilshan_a
Added documentation of output of TestSuite.
587
            
309 by dilshan_a
Added a passed key to return value of problem suite.
588
            if not result_dict['passed']:
589
                passed = False
590
                if stop_on_fail:
591
                    break
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
592
513 by stevenbird
test/test_framework/*, exercises/sample/*
593
        exercise_dict['cases'] = test_case_results
594
        exercise_dict['passed'] = passed
595
        return exercise_dict
294 by mattgiuca
Added application: tutorialservice. Will be used as the Ajax backend for
596
597
    def get_name(self):
598
        return self._name
599
600
##def get_function(filename, function_name):
601
##	import compiler
602
##	mod = compiler.parseFile(filename)
603
##	for node in mod.node.nodes:
604
##		if isinstance(node, compiler.ast.Function) and node.name == function_name:
605
##			return node
606
##