Passed
Push — master ( 523113...818d71 )
by Peter
02:04
created

CmdLine   A

Complexity

Total Complexity 5

Size/Duplication

Total Lines 23
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 23
rs 10
wmc 5

4 Methods

Rating   Name   Duplication   Size   Complexity  
A setUp() 0 3 1
A test_help_call_without_config() 0 4 1
A test_unlock_call_without_lock() 0 4 1
A test_unlock_call_with_lock() 0 5 2
1
'''
2
    Test cases for the executor code.
3
4
    Note the neccessary python search path manipulation below.
5
6
Missing tests:
7
8
  - Makefile in validator package should override student Makefile
9
10
'''
11
12
import os
13
import os.path
14
import sys
15
import logging
16
17
from django.core import mail
18
from django.conf import settings
19
from django.test import TestCase
20
from django.test.utils import override_settings
21
from opensubmit.tests.cases import SubmitStudentScenarioTestCase
22
from django.core.urlresolvers import reverse
23
24
from opensubmit.models import TestMachine, SubmissionTestResult, Submission
25
from opensubmit.tests import utils
26
27
from .helpers.submission import create_validatable_submission
28
from .helpers.submission import create_validated_submission
29
from .helpers.assignment import create_validated_assignment
30
from .helpers.assignment import create_pass_fail_grading
31
from .helpers.djangofiles import create_submission_file
32
from .helpers.user import create_user, get_student_dict
33
from . import uccrap, rootdir
34
35
sys.path.insert(0, os.path.dirname(__file__) + '/../../../executor/')
36
from opensubmitexec import config, cmdline, server, locking, compiler, exceptions  # NOQA
37
38
logger = logging.getLogger('opensubmitexec')
39
40
41
42
class CmdLine(TestCase):
43
    '''
44
    Test cases for the executor command-line script.
45
    '''
46
    def setUp(self):
47
        self.config = config.read_config(
48
            os.path.dirname(__file__) + "/executor.cfg")
49
50
    def test_help_call_without_config(self):
51
        # simulate 'help' call
52
        sys.argv = ['opensubmit-exec', 'help']
53
        cmdline.console_script()
54
55
    def test_unlock_call_without_lock(self):
56
        # simulate 'unlock' call without existing lock
57
        sys.argv = ['opensubmit-exec', 'unlock']
58
        cmdline.console_script()
59
60
    def test_unlock_call_with_lock(self):
61
        # simulate 'unlock' call with existing lock
62
        sys.argv = ['opensubmit-exec', 'unlock']
63
        with locking.ScriptLock(self.config):
64
            cmdline.console_script()
65
66
67
class Library(SubmitStudentScenarioTestCase):
68
    '''
69
    Tests for the executor library functions used by the validator script.
70
    '''
71
    def setUp(self):
72
        settings.MAIN_URL = self.live_server_url
73
        super(Library, self).setUp()
74
        self.config = config.read_config(
75
            os.path.dirname(__file__) + "/executor.cfg",
76
            override_url=self.live_server_url)
77
78
    def _register_executor(self):
79
        server.send_hostinfo(self.config)
80
        return TestMachine.objects.order_by('-last_contact')[0]
81
82
    def _run_executor(self):
83
        return cmdline.download_and_run(self.config)
84
85
    def test_send_result(self):
86
        sf = create_submission_file()
87
        sub = create_validatable_submission(
88
            self.user, self.validated_assignment, sf)
89
        test_machine = self._register_executor()
90
        sub.assignment.test_machines.add(test_machine)
91
92
        job = server.fetch_job(self.config)
93
        self.assertNotEquals(None, job)
94
95
        msg = uccrap + "Message from validation script."
96
97
        job.send_pass_result(info_student=msg)
98
99
        db_entries = SubmissionTestResult.objects.filter(
100
            kind=SubmissionTestResult.VALIDITY_TEST)
101
102
        self.assertEqual(db_entries[0].result, msg)
103
104
    def test_fetch_job_renaming(self):
105
        test_machine = self._register_executor()
106
        self.validated_assignment.test_machines.add(test_machine)
107
        with open(rootdir + "submfiles/validation/1000fff/helloworld.c", 'rb') as f:
108
            self.c.post('/assignments/%s/new/' % self.validated_assignment.pk, {
109
                'notes': 'This is a test submission.',
110
                'authors': str(self.user.pk),
111
                'attachment': f
112
            })
113
        job = server.fetch_job(self.config)
114
        self.assertIn('helloworld.c', os.listdir(job.working_dir))
115
116
    def test_job_attributes(self):
117
        sf = create_submission_file()
118
        sub = create_validatable_submission(
119
            self.user, self.validated_assignment, sf)
120
        test_machine = self._register_executor()
121
        sub.assignment.test_machines.add(test_machine)
122
        job = server.fetch_job(self.config)
123
        self.assertNotEquals(None, job)
124
        self.assertEquals(job.timeout, self.validated_assignment.attachment_test_timeout)
125
        self.assertEquals(job.sub_id, str(sub.pk))
126
        self.assertEquals(job.file_id, str(sf.pk))
127
        self.assertEquals(job.submitter_name, sub.submitter.get_full_name())
128
        self.assertEquals(job.submitter_student_id, str(sub.submitter.profile.student_id))
129
        self.assertEquals(job.submitter_studyprogram, str(sub.submitter.profile.study_program))
130
        self.assertEquals(job.course, str(self.course))
131
        self.assertEquals(job.assignment, str(self.validated_assignment))
132
133
    def test_wrong_compile_call(self):
134
        sf = create_submission_file()
135
        sub = create_validatable_submission(
136
            self.user, self.validated_assignment, sf)
137
        test_machine = self._register_executor()
138
        sub.assignment.test_machines.add(test_machine)
139
        job = server.fetch_job(self.config)
140
        # Missing output
141
        self.assertRaises(exceptions.ValidatorBrokenException, job.run_compiler, inputs=['input.c'])
142
        # Missing input
143
        self.assertRaises(exceptions.ValidatorBrokenException, job.run_compiler, output='output')
144
        # Wrong file, determined by compiler
145
        self.assertRaises(exceptions.WrongExitStatusException, job.run_compiler, inputs=['foo.c'], output='output')
146
147
148
class Validation(TestCase):
149
    '''
150
    Tests for the execution of validation scripts by the executor.
151
    '''
152
153
    def setUp(self):
154
        super(Validation, self).setUp()
155
        self.config = config.read_config(
156
            os.path.dirname(__file__) + "/executor.cfg")
157
158
    def _test_validation_case(self, directory):
159
        '''
160
        Each of the validator.py files uses the Python assert()
161
        statement to check by itself if the result is the expected
162
        one for its case.
163
        '''
164
        base_dir = os.path.dirname(__file__) + '/submfiles/validation/'
165
        cmdline.copy_and_run(self.config, base_dir + directory)
166
167
    def test_0100fff(self):
168
        self._test_validation_case('0100fff')
169
170
    def test_0100tff(self):
171
        self._test_validation_case('0100tff')
172
173
    def test_0100ttf(self):
174
        self._test_validation_case('0100ttf')
175
176
    def test_1000fff(self):
177
        self._test_validation_case('1000fff')
178
179
    def test_1000fft(self):
180
        self._test_validation_case('1000fft')
181
182
    def test_1000tff(self):
183
        self._test_validation_case('1000tff')
184
185
    def test_1000tft(self):
186
        self._test_validation_case('1000tft')
187
188
    def test_1000ttf(self):
189
        self._test_validation_case('1000ttf')
190
191
    def test_1000ttt(self):
192
        self._test_validation_case('1000ttt')
193
194
    def test_1010tff(self):
195
        self._test_validation_case('1010tff')
196
197
    def test_1010ttf(self):
198
        self._test_validation_case('1010ttf')
199
200
    def test_1100tff(self):
201
        self._test_validation_case('1100tff')
202
203
    def test_1100ttf(self):
204
        self._test_validation_case('1100ttf')
205
206
    def test_3000tff(self):
207
        self._test_validation_case('3000tff')
208
209
    def test_3000ttf(self):
210
        self._test_validation_case('3000ttf')
211
212
    def test_3010tff(self):
213
        self._test_validation_case('3010tff')
214
215
    def test_3010ttf(self):
216
        self._test_validation_case('3010ttf')
217
218
    def test_b000tff(self):
219
        self._test_validation_case('b000tff')
220
221
    def test_b010tff(self):
222
        self._test_validation_case('b010tff')
223
224
225
class Communication(SubmitStudentScenarioTestCase):
226
    '''
227
    Tests for the communication of the executor with the OpenSubmit server.
228
    '''
229
230
    def setUp(self):
231
        settings.MAIN_URL = self.live_server_url
232
        super(Communication, self).setUp()
233
        self.config = config.read_config(
234
            os.path.dirname(__file__) + "/executor.cfg",
235
            override_url=self.live_server_url)
236
237
    def _register_executor(self):
238
        server.send_hostinfo(self.config)
239
        return TestMachine.objects.order_by('-last_contact')[0]
240
241
    def _run_executor(self):
242
        return cmdline.download_and_run(self.config)
243
244
    def _register_test_machine(self):
245
        '''
246
        Utility step for a common test case preparation:
247
        - Create validatable submission
248
        - Register a test machine for it
249
        '''
250
        sf = create_submission_file()
251
        sub = create_validatable_submission(
252
            self.user, self.validated_assignment, sf)
253
        test_machine = self._register_executor()
254
        sub.assignment.test_machines.add(test_machine)
255
        return sub
256
257
    def test_register_executor_explicit(self):
258
        machine_count = TestMachine.objects.all().count()
259 View Code Duplication
        assert(self._register_executor().pk)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
260
        self.assertEqual(machine_count + 1, TestMachine.objects.all().count())
261
262
    @override_settings(JOB_EXECUTOR_SECRET='foo')
263
    def test_invalid_secret(self):
264
        self.assertNotEqual(True, self._run_executor())
265
266
    def test_everything_already_tested(self):
267
        create_validated_submission(self.user, self.validated_assignment)
268
        assert(self._register_executor().pk)
269
        self.assertEqual(False, self._run_executor())
270
271
    def test_parallel_executors_test(self):
272
        NUM_PARALLEL = 3
273
        self.validated_assignment.test_machines.add(self._register_executor())
274
        subs = []
275
        for i in range(1, NUM_PARALLEL + 1):
276
            stud = create_user(get_student_dict(i))
277
            self.course.participants.add(stud.profile)
278
            self.course.save()
279 View Code Duplication
            sf = create_submission_file()
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
280
            subs.append(create_validatable_submission(
281
                stud, self.validated_assignment, sf))
282
283
        # Span a number of threads, each triggering the executor
284
        # This only creates a real test case if executor serialization
285
        # is off (see tests/executor.cfg)
286
        return_codes = utils.run_parallel(len(subs), self._run_executor)
287
        self.assertEqual(
288
            len(list(filter((lambda x: x is True), return_codes))),
289
            len(subs))
290
291
        for sub in subs:
292
            results = SubmissionTestResult.objects.filter(
293
                kind=SubmissionTestResult.VALIDITY_TEST
294
            )
295
            self.assertEqual(NUM_PARALLEL, len(results))
296
            self.assertNotEqual(0, len(results[0].result))
297
298
    def test_too_long_validation(self):
299
        grading = create_pass_fail_grading()
300
        assignment = create_validated_assignment(
301
            self.course, grading, "/submfiles/validation/d000fff/", "validator_run.py")
302
        assignment.attachment_test_timeout = 1
303
        assignment.save()
304
        sf = create_submission_file("/submfiles/validation/d000fff/helloworld.c")
305
        sub = create_validatable_submission(
306
            self.user, assignment, sf)
307
        test_machine = self._register_executor()
308
        sub.assignment.test_machines.add(test_machine)
309
310
        # Fire up the executor, should mark the submission as timed out
311
        self.assertEqual(True, self._run_executor())
312
        # Check if timeout marking took place
313
        sub.refresh_from_db()
314
        self.assertEqual(sub.state, Submission.TEST_VALIDITY_FAILED)
315
        text = sub.get_validation_result().result
316
        self.assertIn("took too long", text)
317
318
    def test_too_long_full_test(self):
319
        grading = create_pass_fail_grading()
320
        assignment = create_validated_assignment(
321
            self.course, 
322
            grading, 
323
            "/submfiles/validation/d000fff/",
324
            "validator_build.py",
325
            "validator_run.py")
326
        assignment.attachment_test_timeout = 1
327
        assignment.save()
328
        sf = create_submission_file("/submfiles/validation/d000fff/helloworld.c")
329
        sub = create_validatable_submission(
330
            self.user, assignment, sf)
331
        test_machine = self._register_executor()
332
        sub.assignment.test_machines.add(test_machine)
333
334
        # Fire up the executor for validation
335
        self.assertEqual(True, self._run_executor())
336
        # Fire up the executor for full test
337
        self.assertEqual(True, self._run_executor())
338
        # Check if timeout marking took place
339
        sub.refresh_from_db()
340
        self.assertEqual(sub.state, Submission.TEST_FULL_FAILED)
341
        assert("took too long" in sub.get_fulltest_result().result)
342
343
    def test_single_file_validator_test(self):
344
        sub = self._register_test_machine()
345
346
        self.assertEqual(True, self._run_executor())
347
        results = SubmissionTestResult.objects.filter(
348
            submission_file=sub.file_upload,
349
            kind=SubmissionTestResult.VALIDITY_TEST
350
        )
351
        self.assertEqual(1, len(results))
352
353
        self.assertEqual(True, self._run_executor())
354
        results = SubmissionTestResult.objects.filter(
355
            submission_file=sub.file_upload,
356
            kind=SubmissionTestResult.VALIDITY_TEST
357
        )
358
        self.assertEqual(1, len(results))
359
        self.assertNotEqual(0, len(results[0].result))
360
361
    def test_full_test(self):
362
        sub = self._register_test_machine()
363
        # validation test
364
        self.assertEqual(True, self._run_executor())
365
        # full test
366
        self.assertEqual(True, self._run_executor())
367
        results = SubmissionTestResult.objects.filter(
368
            submission_file=sub.file_upload,
369
            kind=SubmissionTestResult.FULL_TEST
370
        )
371
        self.assertEqual(1, len(results))
372
        self.assertNotEqual(0, len(results[0].result))
373
374
    def test_inconsistent_state_email(self):
375
        '''
376
        Test operator email on inconsistent state.
377
        Since executor execution and submission sending is one step,
378
        we need to mock the incoming invalid executor request.
379
        '''
380
        sf = create_submission_file()
381
        self.sub = create_validatable_submission(
382
            self.user, self.validated_assignment, sf)
383
        test_machine = self._register_executor()
384
        self.sub.assignment.test_machines.add(test_machine)
385
        self.sub.state = Submission.TEST_FULL_PENDING
386
        self.sub.save()
387
        post_data = {'Secret': settings.JOB_EXECUTOR_SECRET,
388
                     'UUID': test_machine.host,
389
                     'Action': 'test_compile',
390
                     'SubmissionFileId': self.sub.file_upload.pk,
391
                     'PerfData': '',
392
                     'ErrorCode': 0,
393
                     'Message': 'In A Bottle'}
394
        self.c.post(reverse('jobs'), post_data)
395
        self.assertNotEqual(len(mail.outbox), 0)
396
        self.assertIn('Action reported by the executor: test_compile',
397
                      mail.outbox[0].message().as_string())
398
399
    def test_assignment_specific_test_machine(self):
400
        # Register two test machines T1 and T2
401
        real_machine = self._register_executor()
402
        fake_machine = TestMachine(host="127.0.0.2")
403
        fake_machine.save()
404
        # Assign each of them to a different assignment
405
        self.open_assignment.test_machines.add(real_machine)
406
        self.validated_assignment.test_machines.add(fake_machine)
407
        # Produce submission for the assignment linked to fake_machine
408
        sub1 = Submission(
409
            assignment=self.validated_assignment,
410
            submitter=self.user,
411
            state=Submission.TEST_VALIDITY_PENDING,
412
            file_upload=create_submission_file()
413
        )
414
        sub1.save()
415
        # Run real_machine executor, should not react on this submission
416
        old_sub1_state = sub1.state
417
        self.assertEqual(False, self._run_executor())
418
        # Make sure that submission object was not touched,
419
        # whatever the executor says
420
        sub1 = Submission.objects.get(pk=sub1.pk)
421
        self.assertEqual(old_sub1_state, sub1.state)
422