# -*- coding: utf-8 -*-
# $Id: JUnit.py 1635 2013-06-16 19:43:19Z amelung $
#
# Copyright (c) 2007-2011 Otto-von-Guericke-Universität Magdeburg
#
# This file is part of ECSpooler.
#
################################################################################
# Changelog #
################################################################################
#
# 04.03.2009, chbauman:
# replaced '/' by join() for better platform independency
# formatted source code
# 16.03.2009, chbauman:
# worked on comments
# changed version
# new function 'getLibInImportPos'
# outhoused RE for importsArray to JUnitConf.py
# improved 'handleStudentsImports'
# improved error message
# removed useless comments
# 17.03.2009, chbauman:
# import junit_libs.*, if no other import is declared
# 30.03.2009, chbauman:
# insertion of imports in handleStudentsImports causes increase of line_offset
# 06.04.2009, chbauman:
# implemented _postProcessCheckSemantics
# 07.04.2009, chbauman:
# added some comments
# all post processors delete string JUnitConf.NS_STUDENT from messages now.
# 30.04.2009, chbauman:
# replaced re.sub whenever possible
# 12.07.2009, amelung:
# renamed JUnitConf to config; moved some settings from config to this file
# 16.05.2010, chbauman:
# New Regular Expression for finding closed multiline comments.
# grantValidPackage handles commented package declarations more gracefully, now.
# Some minor formattings.
import logging
# import config file
## Regular expressions to extract certain information
# CLASS_NAME_RE consists of ClassModifier? class Identifier Super? Interfaces? ClassBody
# (see http://java.sun.com/docs/books/jls/first_edition/html/8.doc.html#15372 [04.03.2009@09:50])
# We are only interested in the public class
# Classnames start with a lowercase letter followed by some other letters
# Generics are strings surrounded by '<' and '>'.
# An Identifier is a name followed by an optional generic argument
# 'extends' followed by an identifier signals that this class inherits from another class
# 'implements' followed by a comma-separated list of identifiers signals wich interfaces a class has
# '{' is sufficient for the (our) body
# Since the class of the name we want to extract definately is public, <ClassModifier> is NOT optional
javaClassDeclaration = '%s\s+class\s+(?P<className>%s)(%s)?\s*(%s)?\s*(%s)?\s*%s' % (javaClassModifier, javaClassName, javaGeneric, javaSuper, javaInterfaces, javaClassBody)
# Determines the student's chosen package
# Finds closed multiline comments (flags indicate multiline and dotall matchings)
# Finds all import declarations excluding packages java.*
# This RE will search for the first two lines of a Failure-Object trace.
# java.lang.ArrayIndexOutOfBoundsException: 2 <- will be matched
# at studentPackage.Matrix.mult(Matrix.java:20) <- will be matched
# at JUnitTester.multAdd(JUnitTester.java:29) <- will NOT be matched
#FAILURE_TRACE_RE = re.compile('(\w|\.)+?:\s\d+(\s\t)*?at\s%s\.\w+?\.\w+?\(\w+?\.\w+?:(?P<number>\d+?)\)' % NS_STUDENT)
"""
Backend class that determines whether a submission of java code
returns expected values which are defined in JUnit tests, or not.
"""
# While preprocessing student's submission it may occur that some lines
# have to be added (like package declarations). In case of failures during
# checks the feedbacks have to be scanned for line numbers and to be
# updated (minus line_offset).
#-------- Constructor --------------------------------------------------------
"""
This constructor is needed to set the logging environment.
"""
#-------- Methods for modifying incomming source code ------------------------
"""
Returns the class name of a given java source.
@param source: Java source code.
@return: Class name of given source code.
"""
assert matcher is not None, \
'Name of the public class could not be extracted from source\n\n%s' % source
"""
Replaces all Variables ${CLASS} with the class name of a given java source.
@param source: Java source code.
@param className: Class name that ${CLASS} will be substituted with.
@return: source with substituted ${CLASS}.
"""
"""
Determines whether source already has a package declaration.
If yes, it will be overwritten with a new declaration.
If not, a new package declaration will be written.
Note, that this method ignores invalid package declarations inside of comments by excluding
lines containing multiline comments in the search string completely and by forcing a package
declaration to not be inside of a single line comment.
@param source: Java source code.
@return: source with valid package declaration.
"""
# Temporarily remove all multiline closed comments:
# Try to find a proper package declaration
if matcher is not None:
# We found a package declaration -> replace it!
else:
# we've inserted two lines of source code:
return tmp_result + source
"""
Searches in importDecl for libName and returns the right-most position.
Since we are working on Java import declarations, we have to search for libName preceeded by
space or a dot and followed by a dot or nothing.
@param libName: Name of library that shall be searched
@param importDecl: A Java import declaration libName will be searched in.
@return: right-most position of libName in importDecl or -1 if not found.
"""
pos = -1
# if libInImports is None, libName is not in importDecl:
if libInImports is not None:
# find right-most position:
return pos
"""
Student's imports should be included by packages which are set
set in Java classpath command line option. Single Java classes
in junit_libs will be included by this method.
@param source: Java source code.
@return: source with special import declarations for junit_libs
"""
# since a valid package is already written, we can access it:
# by adding the new import declaration, line_offset
# increases by 2:
return source
#-------- Syntax methods that have to be overwritten -------------------------
# at the very beginning of our syntax check we set line_offset
# to 0. Setting it to 0 in _postProcessCheckSyntax would lead to an
# accumulation of offsets if a sumbission is syntactically incorrect.
#logging.debug(srcWithValidPackages)
#logging.debug(preProcessedSource)
return preProcessedSource, className
"""
This method subtracts line_offset from the line numbers the compiler
returned in its message.
After that, every occurence of config.NS_STUDENT+'.' will be erased.
@see: ProgrammingBackend._postProcessCheckSyntax
"""
return message
"""
Tests the syntax of a programm.
@param jobId: ID for this test job
@param test: name of the selected test environment (cf. self.testSchema)
@return: a BackendResult or None if test succeeded
"""
# get the compiler or if not available the interpreter
if compiler:
try:
# test term (e.g., student's source code)
try:
except AssertionError, ae:
# guarantee that the submission will be put in folder NS_STUDENT
#logging.debug('xxx: %s' % folder)
src,
#logging.debug('xxx: %s' % module)
#logging.debug('exitcode: %s' % repr(exitcode))
#logging.debug('result: %s' % repr(result))
except Exception, e:
msg = 'Internal error during syntax check: %s: %s' % \
# consider exit code
#return BackendResult(-exitcode, result or repr(-exitcode))
else:
msg = 'No compiler/interpreter defined (test spec: %s).' \
# everything seems to be ok
return None
#-------- Semantic methods that have to be overwritten -----------------------
"""
Checks the semantics of a program.
@param jobId: ID for this job
@return: a BackendResult.
"""
# variable declaration
exitcode = -42
# Test if an InputField exists
assert inputFields, 'No InputFields found!'
# get submission
assert submission is not None, \
message = 'No test specification selected.'
try:
except AssertionError, ae:
# get compiler
# get interpreter
# get template
#----------- compile and run Wrapper Template ------------------------
# replace all variables in wrapper template
# empty fields should cause that no text is written
if field_text is None:
field_text = ""
#wrapper_code = re.sub('\$\{%s\}' % field.getName(),
# field_text,
# wrapper_code)
try:
# compile using javac
#assert exitcode == EX_OK, \
# 'Error in wrapper code during semantic check:\n\n%s' % result
# run using java
except Exception, e:
message = 'Internal error during semantic check: %s: %s' % \
# postprocess the result:
else:
#return BackendResult(True, '\nYour submission passed all tests.')
#return BackendResult(True, result)
"""
This method is used to post process interpreter messages.
Two modifications will be performed:
First, the message will be scanned for a Failure trace. If one exists,
the trace is shortened and line_offset subtracted from the returned line
numbers.
Second, every occurence of config.NS_STUDENT+'.' will be erased.
@see: ProgrammingBackend._postProcessCheckSemantic
"""
# scan for Failure trace:
# if the matcher is not None, there exists an Failure trace:
if matcher is not None:
# don't forget to subtract line_offset from match's line number
# we do not display the whole trace. Show that there was more:
# erase all occurences of config.NS_STUDENT
return message