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.config import Config
27
from ivle.database import Exercise, TestSuite, TestCase, TestSuiteVar, TestCasePart, get_store
29
class XMLMalformedError(Exception):
30
"""Error thrown when encountering malformed data."""
32
def __init__(self, text):
35
def getTextData(element):
36
""" Get the text and cdata inside an element
37
Leading and trailing whitespace are stripped
40
for child in element.childNodes:
41
if child.nodeType == child.CDATA_SECTION_NODE:
43
elif child.nodeType == child.TEXT_NODE:
48
return unicode(data.strip())
50
def add_var(store, var_type, var_name=u"", var_value=u"", arg_no=0):
51
"""Given an var node, parse it into a storm object."""
52
new_var = TestSuiteVar()
53
new_var.var_name = unicode(var_name)
54
new_var.var_value = unicode(var_value)
55
new_var.var_type = unicode(var_type)
56
new_var.arg_no = arg_no
60
def add_test_suite(suite_node, suite_num, store):
61
"""Given a test suite element, get all the cases it contains."""
64
for case_node in suite_node.getElementsByTagName('function'):
65
cases.append(add_test_case(case_node, case_num, store))
69
# stdin - Stdin for the suite - Unique - Text inside element
70
# file - File to add to the filespace - Name - List
71
# var - Variable for the suite - Name/Value - List
72
# arg - Argument to functions - Name/Value - ORDERED List
73
# exception - Allowed exception name - Name - List
74
# function - An actual test case
78
file_nodes = suite_node.getElementsByTagName('file')
79
for file_node in file_nodes:
80
suite_vars.append(add_var(store, 'file', file_node.getAttribute('name')))
83
var_nodes = suite_node.getElementsByTagName('var')
84
for var_node in var_nodes:
85
var_name = var_node.getAttribute('name')
86
var_value = var_node.getAttribute('value')
87
suite_vars.append(add_var(store, 'var', var_name, var_value))
89
# Args need to be numbered as they are found, as this order matters
91
for arg_node in suite_node.getElementsByTagName('arg'):
92
suite_vars.append(add_var(store, 'arg', arg_node.getAttribute('name'),
93
arg_node.getAttribute('value'), arg_num))
96
# Add allowed exceptions
97
exception_nodes = suite_node.getElementsByTagName('exception')
98
for exception_node in exception_nodes:
99
name = exception_node.getAttribute('name')
100
suite_vars.append(add_var(store, 'exception', name))
102
# Can only have 0-1 stdin elements
103
stdin = suite_node.getElementsByTagName('stdin')
105
raise XMLMalformedError('Too many stdin tags found.')
107
stdin = getTextData(stdin[0])
111
new_suite = TestSuite()
112
new_suite.description = unicode(suite_node.getAttribute('name'))
113
new_suite.seq_no = suite_num
114
new_suite.function = unicode(suite_node.getAttribute('function'))
115
new_suite.stdin = unicode(stdin)
116
for testcase in cases:
117
new_suite.test_cases.add(testcase)
118
for var in suite_vars:
119
new_suite.variables.add(var)
123
def add_part(store, part_type, test_type, data, filename=u""):
124
new_part = TestCasePart()
125
new_part.part_type = unicode(part_type)
126
new_part.test_type = unicode(test_type)
127
new_part.data = unicode(data)
128
new_part.filename = unicode(filename)
132
def add_test_case(case_node, case_num, store):
133
"""Given a test case node, parse it int a storm object."""
136
# A function is allowed to contain the following elements
143
allowed_parts = ['stdout', 'stderr', 'result', 'exception', 'file', 'code']
145
for child_node in case_node.childNodes:
146
if child_node.nodeType != child_node.ELEMENT_NODE:
149
if child_node.tagName == 'file':
151
test_type = child_node.getAttribute('type')
152
data = getTextData(child_node)
153
filename = child_node.getAttribute('name')
155
raise XMLMalformedException('file tag must have names')
156
parts.append(add_part(store, part_type, test_type, data,
159
elif child_node.tagName in allowed_parts:
160
part_type = child_node.tagName
161
test_type = child_node.getAttribute('type')
162
data = getTextData(child_node)
163
parts.append(add_part(store, part_type, test_type, data))
165
#Now create the object to hold the data
166
new_test_case = TestCase()
167
new_test_case.passmsg = unicode(case_node.getAttribute(u'pass'))
168
new_test_case.failmsg = unicode(case_node.getAttribute(u'fail'))
169
new_test_case.test_default = unicode(case_node.getAttribute(u'default'))
170
new_test_case.seq_no = case_num
171
store.add(new_test_case)
173
new_test_case.parts.add(part)
176
xmlfile = sys.argv[1]
178
def add_exercise(xmlfile):
179
# Skip existing ones.
180
if store.find(Exercise, id=unicode(xmlfile)).count():
183
print "Adding exercise", xmlfile
185
filedom = minidom.parse(xmlfile)
187
raise Exception('ivle-addexercise: error opening file ' + xmlfile + ': ' + e[1])
189
for child in filedom.childNodes:
190
if child.nodeType == child.ELEMENT_NODE and child.tagName == 'exercise':
193
raise XMLMalformedError('ivle-addexercise: error parsing XML: root node must be "exercise"')
195
exercisename = exercise.getAttribute('name')
196
rows = exercise.getAttribute('rows')
200
partial_solution = None
203
test_suite_nodes = []
204
for child in exercise.childNodes:
205
if child.nodeType != child.ELEMENT_NODE:
207
if child.tagName == 'solution':
208
if solution is not None:
209
raise XMLMalformedError('ivle-addexercise: error parsing XML: multiple "solution" nodes')
210
solution = getTextData(child)
211
elif child.tagName == 'include':
212
if include_code is not None:
213
raise XMLMalformedError('ivle-addexercise: error parsing XML: multiple "include" nodes')
214
include_code = getTextData(child)
215
elif child.tagName == 'partial':
216
if partial_solution is not None:
217
raise XMLMalformedError('ivle-addexercise: error parsing XML: multiple "include" nodes')
218
partial_solution = getTextData(child)
219
elif child.tagName == 'case':
220
test_suite_nodes.append(child)
221
elif child.tagName == 'desc':
222
description = getTextData(child)
224
new_exercise = Exercise()
225
new_exercise.id = unicode(xmlfile)
226
new_exercise.name = exercisename
227
new_exercise.num_rows = int(rows)
228
new_exercise.partial = partial_solution
229
new_exercise.solution = solution
230
new_exercise.include = include_code
231
new_exercise.description = description
232
new_exercise.partial = partial_solution
233
store.add(new_exercise)
235
for suite in test_suite_nodes:
236
new_exercise.test_suites.add(add_test_suite(suite, suite_num, store))
239
store.add(new_exercise)
242
store = get_store(Config())
244
xmlfiles = sys.argv[1:]
245
for xmlfile in xmlfiles:
247
add_exercise(xmlfile)
249
print "ERROR: Could not add file", xmlfile