2
# IVLE - Informatics Virtual Learning Environment
3
# Copyright (C) 2007-2009 The University of Melbourne
5
# This program is free software; you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 2 of the License, or
8
# (at your option) any later version.
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
15
# You should have received a copy of the GNU General Public License
16
# along with this program; if not, write to the Free Software
17
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19
# Author: Nicholas Chadwick
21
"""Script to upload an exercise file into the database"""
23
import os, sys, traceback
24
import xml.dom.minidom as minidom
26
from ivle.database import Exercise, TestSuite, TestCase, TestSuiteVar, TestCasePart, get_store
28
class XMLMalformedError(Exception):
29
"""Error thrown when encountering malformed data."""
31
def __init__(self, text):
34
def getTextData(element):
35
""" Get the text and cdata inside an element
36
Leading and trailing whitespace are stripped
39
for child in element.childNodes:
40
if child.nodeType == child.CDATA_SECTION_NODE:
42
elif child.nodeType == child.TEXT_NODE:
47
return unicode(data.strip())
49
def add_var(store, var_type, var_name=u"", var_value=u"", arg_no=0):
50
"""Given an var node, parse it into a storm object."""
51
new_var = TestSuiteVar()
52
new_var.var_name = unicode(var_name)
53
new_var.var_value = unicode(var_value)
54
new_var.var_type = unicode(var_type)
55
new_var.arg_no = arg_no
59
def add_test_suite(suite_node, suite_num, store):
60
"""Given a test suite element, get all the cases it contains."""
63
for case_node in suite_node.getElementsByTagName('function'):
65
cases.append(add_test_case(case_node, case_num, store))
68
# stdin - Stdin for the suite - Unique - Text inside element
69
# file - File to add to the filespace - Name - List
70
# var - Variable for the suite - Name/Value - List
71
# arg - Argument to functions - Name/Value - ORDERED List
72
# exception - Allowed exception name - Name - List
73
# function - An actual test case
77
file_nodes = suite_node.getElementsByTagName('file')
78
for file_node in file_nodes:
79
suite_vars.append(add_var(store, 'file', file_node.getAttribute('name')))
82
var_nodes = suite_node.getElementsByTagName('var')
83
for var_node in var_nodes:
84
var_name = var_node.getAttribute('name')
85
var_value = var_node.getAttribute('value')
86
suite_vars.append(add_var(store, 'var', var_name, var_value))
88
# Args need to be numbered as they are found, as this order matters
90
for arg_node in suite_node.getElementsByTagName('arg'):
91
suite_vars.append(add_var(store, 'arg', arg_node.getAttribute('name'),
92
arg_node.getAttribute('value'), arg_num))
95
# Add allowed exceptions
96
exception_nodes = suite_node.getElementsByTagName('exception')
97
for exception_node in exception_nodes:
98
name = exception_node.getAttribute('name')
99
suite_vars.append(add_var(store, 'exception', name))
101
# Can only have 0-1 stdin elements
102
stdin = suite_node.getElementsByTagName('stdin')
104
raise XMLMalformedError('Too many stdin tags found.')
106
stdin = getTextData(stdin[0])
110
new_suite = TestSuite()
111
new_suite.description = unicode(suite_node.getAttribute('name'))
112
new_suite.seq_no = suite_num
113
new_suite.function = unicode(suite_node.getAttribute('function'))
114
new_suite.stdin = unicode(stdin)
115
for testcase in cases:
116
new_suite.test_cases.add(testcase)
117
for var in suite_vars:
118
new_suite.variables.add(var)
122
def add_part(store, part_type, test_type, data, filename=u""):
123
new_part = TestCasePart()
124
new_part.part_type = unicode(part_type)
125
new_part.test_type = unicode(test_type)
126
new_part.data = unicode(data)
127
new_part.filename = unicode(filename)
131
def add_test_case(case_node, case_num, store):
132
"""Given a test case node, parse it int a storm object."""
135
# A function is allowed to contain the following elements
142
allowed_parts = ['stdout', 'stderr', 'result', 'exception', 'file', 'code']
144
for child_node in case_node.childNodes:
145
if child_node.nodeType != child_node.ELEMENT_NODE:
148
if child_node.tagName == 'file':
150
test_type = child_node.getAttribute('type')
151
data = getTextData(child_node)
152
filename = child_node.getAttribute('name')
154
raise XMLMalformedException('file tag must have names')
155
parts.append(add_part(store, part_type, test_type, data,
158
elif child_node.tagName in allowed_parts:
159
part_type = child_node.tagName
160
test_type = child_node.getAttribute('type')
161
data = getTextData(child_node)
162
parts.append(add_part(store, part_type, test_type, data))
164
#Now create the object to hold the data
165
new_test_case = TestCase()
166
new_test_case.passmsg = unicode(case_node.getAttribute(u'pass'))
167
new_test_case.failmsg = unicode(case_node.getAttribute(u'fail'))
168
new_test_case.test_default = unicode(case_node.getAttribute(u'default'))
169
new_test_case.seq_no = case_num
170
store.add(new_test_case)
172
new_test_case.parts.add(part)
175
xmlfile = sys.argv[1]
177
def add_exercise(xmlfile):
180
# Skip existing ones.
181
if store.find(Exercise, id=unicode(xmlfile)).count():
184
print "Adding exercise", xmlfile
186
filedom = minidom.parse(xmlfile)
188
raise Exception('ivle-addexercise: error opening file ' + xmlfile + ': ' + e[1])
190
for child in filedom.childNodes:
191
if child.nodeType == child.ELEMENT_NODE and child.tagName == 'exercise':
194
raise XMLMalformedError('ivle-addexercise: error parsing XML: root node must be "exercise"')
196
exercisename = exercise.getAttribute('name')
197
rows = exercise.getAttribute('rows')
201
partial_solution = None
204
test_suite_nodes = []
205
for child in exercise.childNodes:
206
if child.nodeType != child.ELEMENT_NODE:
208
if child.tagName == 'solution':
209
if solution is not None:
210
raise XMLMalformedError('ivle-addexercise: error parsing XML: multiple "solution" nodes')
211
solution = getTextData(child)
212
elif child.tagName == 'include':
213
if include_code is not None:
214
raise XMLMalformedError('ivle-addexercise: error parsing XML: multiple "include" nodes')
215
include_code = getTextData(child)
216
elif child.tagName == 'partial':
217
if partial_solution is not None:
218
raise XMLMalformedError('ivle-addexercise: error parsing XML: multiple "include" nodes')
219
partial_solution = getTextData(child)
220
elif child.tagName == 'case':
221
test_suite_nodes.append(child)
222
elif child.tagName == 'desc':
223
description = getTextData(child)
225
new_exercise = Exercise()
226
new_exercise.id = unicode(xmlfile)
227
new_exercise.name = exercisename
228
new_exercise.num_rows = int(rows)
229
new_exercise.partial = partial_solution
230
new_exercise.solution = solution
231
new_exercise.include = include_code
232
new_exercise.description = description
233
new_exercise.partial = partial_solution
234
store.add(new_exercise)
236
for suite in test_suite_nodes:
237
new_exercise.test_suites.add(add_test_suite(suite, suite_num, store))
240
store.add(new_exercise)
243
xmlfiles = sys.argv[1:]
244
for xmlfile in xmlfiles:
246
add_exercise(xmlfile)
248
print "ERROR: Could not add file", xmlfile